Last active
April 20, 2017 05:23
-
-
Save Everlag/2b76d073f890d2f23cdc0e56c2cef156 to your computer and use it in GitHub Desktop.
Naive GOAP(both buggy and slow)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
// This implements a half broken version of extremely | |
// naive flavor of Goal Oriented Action Planning | |
// Based off of https://gamedevelopment.tutsplus.com/tutorials/goal-oriented-action-planning-for-a-smarter-ai--cms-20793 | |
import ( | |
"fmt" | |
"strings" | |
) | |
type WorldState struct { | |
hasAxe bool | |
} | |
type UnitState struct { | |
hasAxe bool | |
hasWood bool | |
} | |
type PreCondition interface { | |
Satisfied(UnitState, WorldState) bool | |
} | |
type axeAvailable struct{} | |
func (pc axeAvailable) Satisfied(u UnitState, w WorldState) bool { | |
return w.hasAxe | |
} | |
type notHoldingAxe struct{} | |
func (pc notHoldingAxe) Satisfied(u UnitState, w WorldState) bool { | |
return !u.hasAxe | |
} | |
type isHoldingAxe struct{} | |
func (pc isHoldingAxe) Satisfied(u UnitState, w WorldState) bool { | |
return u.hasAxe | |
} | |
type Effect interface { | |
Apply(*UnitState, *WorldState) | |
} | |
type gotAxe struct{} | |
func (a gotAxe) Apply(u *UnitState, w *WorldState) { | |
w.hasAxe = false | |
u.hasAxe = true | |
} | |
type gotWood struct{} | |
func (a gotWood) Apply(u *UnitState, w *WorldState) { | |
u.hasWood = true | |
} | |
type Action interface { | |
PreConditions() []PreCondition | |
Effects() []Effect | |
Cost() uint | |
String() string | |
} | |
func CanRun(a Action, u UnitState, w WorldState) bool { | |
cons := a.PreConditions() | |
for _, c := range cons { | |
if !c.Satisfied(u, w) { | |
return false | |
} | |
} | |
return true | |
} | |
type getAxe struct{} | |
func (a getAxe) PreConditions() []PreCondition { | |
return []PreCondition{ | |
axeAvailable{}, notHoldingAxe{}, | |
} | |
} | |
func (a getAxe) Effects() []Effect { | |
return []Effect{ | |
gotAxe{}, | |
} | |
} | |
func (a getAxe) Cost() uint { | |
return 1 | |
} | |
func (a getAxe) String() string { | |
return "get axe" | |
} | |
type chopWood struct{} | |
func (a chopWood) PreConditions() []PreCondition { | |
return []PreCondition{ | |
isHoldingAxe{}, | |
} | |
} | |
func (a chopWood) Effects() []Effect { | |
return []Effect{ | |
gotWood{}, | |
} | |
} | |
func (a chopWood) Cost() uint { | |
return 1 | |
} | |
func (a chopWood) String() string { | |
return "chop wood" | |
} | |
type Goal PreCondition | |
type GetWood struct{} | |
func (g GetWood) Satisfied(u UnitState, w WorldState) bool { | |
return u.hasWood | |
} | |
type Plan struct { | |
actions []Action | |
cost uint | |
} | |
func (p *Plan) Push(as ...Action) { | |
p.actions = append(p.actions, as...) | |
for _, a := range as { | |
p.cost += a.Cost() | |
} | |
} | |
func (p *Plan) String() string { | |
aStrings := make([]string, len(p.actions)) | |
for i, a := range p.actions { | |
aStrings[i] = a.String() | |
} | |
return strings.Join(aStrings, " | ") | |
} | |
// NOTE: current implementation is O(n^2) and could be reduced to an A* | |
// as this is just a search problem. | |
// | |
// Also, I'm pretty sure this just doesn't actually work but I had 2 exams | |
// today and its late... | |
func getPlan(g Goal, actions []Action, u UnitState, w WorldState) *Plan { | |
var plan Plan | |
for i, a := range actions { | |
available := make([]Action, len(actions)-1)[:0] | |
available = append(available, actions[:i]...) | |
available = append(available, actions[i+1:]...) | |
tempW := w | |
tempU := u | |
fmt.Printf("trying %s\n", a.String()) | |
if !CanRun(a, tempU, tempW) { | |
fmt.Printf("cannot run %s\n", a.String()) | |
continue | |
} | |
effects := a.Effects() | |
for _, e := range effects { | |
e.Apply(&tempU, &tempW) | |
} | |
if g.Satisfied(tempU, tempW) { | |
plan.Push(a) | |
fmt.Printf("found '%s' to satisfy\n", a.String()) | |
break | |
} | |
next:= getPlan(g, available, tempU, tempW) | |
if next != nil { | |
// Initial action prompting that plan | |
plan.Push(a) | |
// And whatever resulted | |
plan.Push(next.actions...) | |
} | |
} | |
return &plan | |
} | |
func main() { | |
w := WorldState{true} | |
u := UnitState{ | |
hasAxe: false, | |
hasWood: false, | |
} | |
// If you take chopWood out, it still makes a plan... | |
// the wrong plan... huh | |
actions := []Action{&chopWood{}, &getAxe{}} | |
g := GetWood{} | |
plan := getPlan(g, actions, u, w) | |
fmt.Println(plan) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment