diff --git a/ai/README.md b/ai/README.md deleted file mode 100644 index 65a723f6..00000000 --- a/ai/README.md +++ /dev/null @@ -1,29 +0,0 @@ -The ai package provides a way of having a program run user-code at runtime. This is done using two separate packages, yedparse and polish. - - yedparse: github.com/runningwild/yedparse - polish: github.com/runningwild/Polish - - -An ai script is supplied as a yed graph saved as a .xgml file. The format is as follows: - - * There must be exactly one start node, specified by being labeled with the text 'start', this node should have exactly one edge leading to another node. It is this node at which the graph will begin evaluating. - * Each node should have a single expression in polish notation that will be evaluated using the polish package. The functions available to the expression should be known a-priori. - * Each node may have any number of output edges colored black. - * Each node may have output edges colored either red or green, but if there are any red edges there must be at least one green edge and vice-versa. - * If there are any red or green output edges from a node then when that node's expression is evaluated it must evaluate to either true or false. If it evaluates to true then an edge is randomly selected among all output edges that are colored either green or black. If it evaluates to false then an edge is randomly selected among all output edges that are colored either red or black. The selected edge is followed to find the next node to evaluate. - * If there are no output edges that are colored green or red then when that node's expression is evaluated it does not need to evaluate to anything. After evaluating an edge is randomly selected out of all output edges (which are implicitly colored black), and that edge is followed to find the next node to evaluate. - - -![sample ai graph](/runningwild/go-glop/raw/master/ai/sample.png) - -The following functions and variables are used in the above graph: - - numVisibleEnemies - distBetween - nearestEnemy - attack - advanceTowards - done - me - -There are two nodes with red and green output edges, their expressions both evaluate to a boolean value. There are two nodes (other than the start node) with only black output edges, these nodes do not evaluate to boolean values, but that is ok since the result of the expression will never be checked since there are no red or green output edges. diff --git a/ai/ai.go b/ai/ai.go deleted file mode 100644 index 77705c67..00000000 --- a/ai/ai.go +++ /dev/null @@ -1,132 +0,0 @@ -package ai - -import ( - "fmt" - "github.com/MobRulesGames/yedparse" - "github.com/MobRulesGames/polish" - "math/rand" -) - -type Error struct { - ErrorString string -} -func (e *Error) Error() string { - return e.ErrorString -} - -var InterruptError error = &Error{ "Evaluation was terminated due to an interrupt." } -var TermError error = &Error{ "Evaluation was terminated early." } -var StartError error = &Error{ "No start node was found." } - -type AiGraph struct { - Graph *yed.Graph - Context *polish.Context - - // If a signal is sent along this channel it will terminate evaluation with - // the error that was sent - term chan error -} - -func NewGraph() *AiGraph { - return &AiGraph{ - term: make(chan error, 1), - } -} - -func (aig *AiGraph) Term() chan<- error { - return aig.term -} - -func (aig *AiGraph) subEval(labels *[]string, node *yed.Node) (out_node *yed.Node, err error) { - defer func() { - if r := recover(); r != nil { - err = &Error{fmt.Sprintf("%v", r)} - } - } () - select { - case err := <-aig.term: - return nil, err - - default: - } - *labels = append(*labels, node.Label()) - res, err := aig.Context.Eval(node.Label()) - if err != nil { - return nil, err - } - var red,green,black []*yed.Edge - for i := 0; i < node.NumOutputs(); i++ { - edge := node.Output(i) - r,g,b,_ := edge.RGBA() - if r > 200 && g < 100 && b < 100 { - red = append(red, edge) - } else if g > 200 && r < 100 && b < 100 { - green = append(green, edge) - } else { - black = append(black, edge) - } - } - if (len(red) == 0) != (len(green) == 0) { - panic("A node cannot have red edges without green edges or vice versa.") - } - - // A node can have green, red, and black edges. In this case the condition - // will be evaluated and if true either a green or black edge will be - // traversed, and if false a red or black edge will be traversed. - - var edges []*yed.Edge - if len(green) > 0 { - for _,edge := range black { - red = append(red, edge) - green = append(green, edge) - } - if len(res) != 1 { - panic("Needed to evaluate a node, but it didn't leave exactly one value after evalutation.") - } - if res[0].Bool() { - edges = green - } else { - edges = red - } - } else { - edges = black - } - - if len(edges) == 0 { - return nil, nil - } - follow := edges[rand.Intn(len(edges))] - return follow.Dst(), nil -} - -// chunk_size is the number of nodes that will be evaluated at a time. -// After that many nodes are evaluated, if evaluation has not already -// terminated, cont will be called and evaluation will only continue if -// cont returns true. -func (aig *AiGraph) Eval(chunk_size int, cont func() bool) ([]string, error) { - var labels []string - var node *yed.Node - for i := 0; i < aig.Graph.NumNodes(); i++ { - node = aig.Graph.Node(i) - if node.Label() == "start" { - break - } - } - if node == nil || node.NumOutputs() == 0 { - return labels, StartError - } - node = node.Output(0).Dst() - var err error - for node != nil { - for i := 0; i < chunk_size && node != nil; i++ { - node, err = aig.subEval(&labels, node) - if err != nil { - return labels, err - } - } - if !cont() { - break - } - } - return labels, nil -} diff --git a/ai/ai_test.go b/ai/ai_test.go deleted file mode 100644 index 09c2c78c..00000000 --- a/ai/ai_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package ai_test - -import ( - . "github.com/orfjackal/gospec/src/gospec" - "github.com/orfjackal/gospec/src/gospec" - "github.com/MobRulesGames/yedparse" - "github.com/MobRulesGames/polish" - "github.com/MobRulesGames/glop/ai" -) - -func AiSpec(c gospec.Context) { - c.Specify("Load a simple .xgml file.", func() { - g, err := yed.ParseFromFile("state.xgml") - c.Assume(err, Equals, nil) - aig := ai.NewGraph() - aig.Graph = &g.Graph - aig.Context = polish.MakeContext() - polish.AddIntMathContext(aig.Context) - - dist := 0 - dist_func := func() int { - return dist - } - - var nearest int = 7 - nearest_func := func() int { - return nearest - } - - attacks := 0 - attack_func := func() int { - attacks++ - return 0 - } - - aig.Context.AddFunc("dist", dist_func) - aig.Context.AddFunc("nearest", nearest_func) - aig.Context.AddFunc("move", func() int { nearest--; return 0 }) - aig.Context.AddFunc("wait", func() int { return 0 }) - aig.Context.AddFunc("attack", attack_func) - aig.Eval(2, func() bool { return true }) - - c.Expect(attacks, Equals, 0) - c.Expect(nearest, Equals, 4) - }) -} - -func TermSpec(c gospec.Context) { - g, err := yed.ParseFromFile("state.xgml") - c.Assume(err, Equals, nil) - aig := ai.NewGraph() - aig.Graph = &g.Graph - aig.Context = polish.MakeContext() - polish.AddIntMathContext(aig.Context) - polish.AddIntMathContext(aig.Context) - c.Specify("Calling AiGraph.Term() will terminate evaluation early.", func() { - var nearest int = 7 - nearest_func := func() int { - return nearest - } - - dist := 0 - term := true - dist_func := func() int { - if nearest == 6 && term { - aig.Term() <- nil - } - return dist - } - - attacks := 0 - attack_func := func() int { - attacks++ - return 0 - } - - aig.Context.AddFunc("dist", dist_func) - aig.Context.AddFunc("nearest", nearest_func) - aig.Context.AddFunc("move", func() int { nearest--; return 0 }) - aig.Context.AddFunc("wait", func() int { return 0 }) - aig.Context.AddFunc("attack", attack_func) - aig.Eval(2, func() bool { return true }) - - c.Expect(attacks, Equals, 0) - c.Expect(nearest, Equals, 6) - - term = false - aig.Eval(2, func() bool { return true }) - c.Expect(nearest, Equals, 4) - }) -} - -func ChunkSpec(c gospec.Context) { - g, err := yed.ParseFromFile("state.xgml") - c.Assume(err, Equals, nil) - aig := ai.NewGraph() - aig.Graph = &g.Graph - aig.Context = polish.MakeContext() - polish.AddIntMathContext(aig.Context) - polish.AddIntMathContext(aig.Context) - c.Specify("cont() returning false will terminate evaluation early.", func() { - var nearest int = 7 - nearest_func := func() int { - return nearest - } - - dist := 0 - term := true - dist_func := func() int { - if nearest == 6 && term { - aig.Term() <- nil - } - return dist - } - - attacks := 0 - attack_func := func() int { - attacks++ - return 0 - } - - aig.Context.AddFunc("dist", dist_func) - aig.Context.AddFunc("nearest", nearest_func) - aig.Context.AddFunc("move", func() int { nearest--; return 0 }) - aig.Context.AddFunc("wait", func() int { return 0 }) - aig.Context.AddFunc("attack", attack_func) - _, err := aig.Eval(4, func() bool { return false }) - // Only have time for 1 move before we terminate early - c.Expect(err, Equals, nil) - c.Expect(nearest, Equals, 6) - }) -} \ No newline at end of file diff --git a/ai/all_specs_test.go b/ai/all_specs_test.go deleted file mode 100644 index bb4646d3..00000000 --- a/ai/all_specs_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package ai_test - -import ( - "github.com/orfjackal/gospec/src/gospec" - "testing" -) - - -func TestAllSpecs(t *testing.T) { - r := gospec.NewRunner() - r.AddSpec(AiSpec) - r.AddSpec(TermSpec) - r.AddSpec(ChunkSpec) - gospec.MainGoTest(r, t) -} - diff --git a/ai/sample.png b/ai/sample.png deleted file mode 100644 index c44903fc..00000000 Binary files a/ai/sample.png and /dev/null differ diff --git a/ai/state.xgml b/ai/state.xgml deleted file mode 100644 index 64bea13e..00000000 --- a/ai/state.xgml +++ /dev/null @@ -1,255 +0,0 @@ - -
- yFiles - 2.8 -
- 1 - - 1 -
- 0 - < 2 dist -
- 79.0 - -300.0 - 128.0 - 30.0 - rectangle - #FFCC00 - #000000 -
-
- < 2 dist - 12 - Dialog - c -
-
-
- 1 - > nearest 4 -
- 180.0 - -240.0 - 128.0 - 30.0 - rectangle - #FFCC00 - #000000 -
-
- > nearest 4 - 12 - Dialog - c -
-
-
- 2 - attack -
- 180.0 - -90.0 - 128.0 - 30.0 - rectangle - #FFCC00 - #000000 -
-
- attack - 12 - Dialog - c -
-
-
- 3 - move -
- 270.0 - -196.0 - 128.0 - 30.0 - rectangle - #FFCC00 - #000000 -
-
- move - 12 - Dialog - c -
-
-
- 4 - wait -
- 270.0 - -150.0 - 128.0 - 30.0 - rectangle - #FFCC00 - #000000 -
-
- wait - 12 - Dialog - c -
-
-
- 5 - start -
- 79.0 - -401.0 - 128.0 - 30.0 - rectangle - #FFCC00 - #000000 -
-
- start - 12 - Dialog - c -
-
-
- 0 - 1 -
- #FF0000 - standard -
-
- 79.0 - -300.0 -
-
- 90.0 - -240.0 -
-
- 180.0 - -240.0 -
-
-
-
- 0.171875 -
-
-
- 0 - 2 -
- #00FF00 - standard -
-
- 79.0 - -300.0 -
-
- 60.0 - -90.0 -
-
- 180.0 - -90.0 -
-
-
-
- -0.296875 -
-
-
- 1 - 3 -
- #00FF00 - standard -
-
- 180.0 - -240.0 -
-
- 180.0 - -196.0 -
-
- 270.0 - -196.0 -
-
-
-
-
- 1 - 4 -
- #FF0000 - standard -
-
- 180.0 - -240.0 -
-
- 150.0 - -150.0 -
-
- 270.0 - -150.0 -
-
-
-
- -0.46875 -
-
-
- 5 - 0 -
- #000000 - standard -
-
-
- 3 - 0 -
- #000000 - standard -
-
- 270.0 - -196.0 -
-
- 270.0 - -311.5 -
-
- 79.0 - -300.0 -
-
-
-
- 0.4765625 - -0.7666666507720947 -
-
-
-
diff --git a/sprite/sprite.go b/sprite/sprite.go index 0cb763c6..88596dfa 100644 --- a/sprite/sprite.go +++ b/sprite/sprite.go @@ -848,7 +848,7 @@ func (s *Sprite) Think(dt int64) { s.waiters[i].states = nil } } - algorithm.Choose(&s.waiters, func(w *waiter) bool { + algorithm.Choose2(&s.waiters, func(w *waiter) bool { return w.states != nil }) } diff --git a/util/algorithm/generic.go b/util/algorithm/generic.go index 96849aa6..a9ba4a5e 100644 --- a/util/algorithm/generic.go +++ b/util/algorithm/generic.go @@ -7,11 +7,37 @@ import ( type Chooser func(interface{}) bool +// Given a slice and a Chooser, returns a slice of the same type as the input +// slice that contains only those elements of the input slice for which +// choose() returns true. The elements of the returned slice will be in the +// same order that they were in in the input slice. +func Choose(_a interface{}, choose Chooser) interface{} { + a := reflect.ValueOf(_a) + if a.Kind() != reflect.Slice { + panic(fmt.Sprintf("Can only Choose from a slice, not a %v", a)) + } + count := 0 + for i := 0; i < a.Len(); i++ { + if choose(a.Index(i).Interface()) { + count++ + } + } + ret := reflect.MakeSlice(a.Type(), count, count) + cur := 0 + for i := 0; i < a.Len(); i++ { + if choose(a.Index(i).Interface()) { + ret.Index(cur).Set(a.Index(i)) + cur++ + } + } + return ret.Interface() +} + // // Given a pointer to a slice and a Chooser, type as the input // // slice that contains only those elements of the input slice for which // // choose() returns true. The elements of the returned slice will be in the // // same order that they were in in the input slice. -func Choose(_a interface{}, chooser interface{}) { +func Choose2(_a interface{}, chooser interface{}) { a := reflect.ValueOf(_a) if a.Kind() != reflect.Ptr || a.Elem().Kind() != reflect.Slice { panic(fmt.Sprintf("Can only Choose from a pointer to a slice, not a %v", a)) @@ -40,17 +66,37 @@ func Choose(_a interface{}, chooser interface{}) { out = c.Call(in) if out[0].Bool() { if count > 0 { - slice.Index(i - count).Set(slice.Index(i)) + slice.Index(i-count).Set(slice.Index(i)) } } else { count++ } } - slice.Set(slice.Slice(0, slice.Len()-count)) + slice.Set(slice.Slice(0, slice.Len() - count)) } type Mapper func(a interface{}) interface{} +func Map(_a interface{}, _b interface{}, mapper Mapper) interface{} { + a := reflect.ValueOf(_a) + if a.Kind() != reflect.Slice { + panic(fmt.Sprintf("Can only Map from a slice, not a %v", a)) + } + + b := reflect.ValueOf(_b) + if b.Kind() != reflect.Slice { + panic(fmt.Sprintf("Can only Map to a slice, not a %v", b)) + } + + ret := reflect.MakeSlice(b.Type(), a.Len(), a.Len()) + for i := 0; i < a.Len(); i++ { + el := reflect.ValueOf(mapper(a.Index(i).Interface())) + ret.Index(i).Set(el) + } + + return ret.Interface() +} + func Map2(_in interface{}, _out interface{}, mapper interface{}) { in := reflect.ValueOf(_in) if in.Kind() != reflect.Slice { @@ -90,3 +136,11 @@ func Map2(_in interface{}, _out interface{}, mapper interface{}) { out.Elem().Index(i).Set(v[0]) } } + + + + + + + +