Skip to content

Instantly share code, notes, and snippets.

@Everlag
Last active April 20, 2017 05:23
Show Gist options
  • Save Everlag/2b76d073f890d2f23cdc0e56c2cef156 to your computer and use it in GitHub Desktop.
Save Everlag/2b76d073f890d2f23cdc0e56c2cef156 to your computer and use it in GitHub Desktop.
Naive GOAP(both buggy and slow)
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