Skip to content

Instantly share code, notes, and snippets.

@01010111
Last active August 8, 2018 18:35
Show Gist options
  • Save 01010111/79b7376dfdbed53a30f304d39e9f2195 to your computer and use it in GitHub Desktop.
Save 01010111/79b7376dfdbed53a30f304d39e9f2195 to your computer and use it in GitHub Desktop.
Haxe GOAP
package util;
using util.Goap.Planner;
class Goap {}
class Planner
{
public static function plan(agent:IGoapAgent):Null<GoapTask>
{
var satisfactory_tasks = agent.get_available_tasks().get_satisfactory_tasks();
if (satisfactory_tasks.length > 0) return satisfactory_tasks[0];
return agent.find_best_next_step();
}
static function find_best_next_step(agent:IGoapAgent):Null<GoapTask>
{
var goal = [ENEMY_DEFEATED];
var queue = agent.get_required_task_set(goal);
var task:GoapTask;
while(queue.length > 0)
{
queue.sort((task1:GoapTask, task2:GoapTask) -> return task1.cost < task2.cost ? -1 : 1);
task = queue.shift();
goal = agent.get_prerequisites(task);
if (goal.length == 0) return task;
for (task in agent.get_required_task_set(goal)) queue.push(task);
}
return null;
}
static function get_available_tasks(agent:IGoapAgent):Array<GoapTask>
{
var available_tasks = [];
for (task in agent.job.tasks)
{
var add = true;
for (prerequisite in task.prerequisites) add ? add = agent.state[prerequisite] : break;
if (add) available_tasks.push(task);
}
return available_tasks;
}
static function get_satisfactory_tasks(tasks:Array<GoapTask>):Array<GoapTask>
{
var satisfactory_tasks = [ for (task in tasks) if (task.effects.indexOf(ENEMY_DEFEATED) >= 0) task ];
satisfactory_tasks.sort((task1:GoapTask, task2:GoapTask) -> return task1.cost < task2.cost ? -1 : 1);
return satisfactory_tasks;
}
static function get_required_task_set(agent:IGoapAgent, requirements:Array<EGoapState>):Array<GoapTask>
{
var required_task_set = [];
for (task in agent.job.tasks) for (requirement in requirements) if (task.effects.indexOf(requirement) >= 0) required_task_set.push(task);
return required_task_set;
}
static function get_prerequisites(agent:IGoapAgent, task:GoapTask):Array<EGoapState>
{
var prerequisites = [];
for (prerequisite in task.prerequisites) if (!agent.state[prerequisite]) prerequisites.push(prerequisite);
return prerequisites;
}
}
class GoapJobs
{
public static var JOBS:Map<EGoapJob, GoapJob> = [
COMMS => {
name: COMMS,
tasks: [
ATTACK => { name: ATTACK, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_SPOTTED, BACKUP_REQUESTED], effects: [ENEMY_DEFEATED], cost: 0 },
REQUEST_BACKUP => { name: REQUEST_BACKUP, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_SPOTTED], effects: [BACKUP_REQUESTED], cost: 1 },
HUNT => { name: HUNT, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_DETECTED], effects: [ENEMY_SPOTTED], cost: 2 },
PATROL => { name: PATROL, prerequisites: [HAS_WEAPON, HAS_AMMO], effects: [ENEMY_DETECTED], cost: 3 },
GET_AMMO => { name: GET_AMMO, prerequisites: [HAS_WEAPON], effects: [HAS_AMMO], cost: 4 },
GET_WEAPON => { name: GET_WEAPON, prerequisites: [], effects: [HAS_WEAPON], cost: 5 },
]
},
SOLDIER => {
name: SOLDIER,
tasks: [
ATTACK => { name: ATTACK, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_SPOTTED], effects: [ENEMY_DEFEATED], cost: 0 },
HUNT => { name: HUNT, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_DETECTED, BACKUP_REQUESTED], effects: [ENEMY_SPOTTED], cost: 1 },
REQUEST_BACKUP => { name: REQUEST_BACKUP, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_DETECTED], effects: [BACKUP_REQUESTED], cost: 2 },
PATROL => { name: PATROL, prerequisites: [HAS_WEAPON, HAS_AMMO], effects: [ENEMY_DETECTED], cost: 3 },
GET_AMMO => { name: GET_AMMO, prerequisites: [HAS_WEAPON], effects: [HAS_AMMO], cost: 4 },
GET_WEAPON => { name: GET_WEAPON, prerequisites: [], effects: [HAS_WEAPON], cost: 5 },
]
},
BACKUP => {
name: BACKUP,
tasks: [
ATTACK => { name: ATTACK, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_SPOTTED], effects: [ENEMY_DEFEATED], cost: 0 },
FOLLOW => { name: FOLLOW, prerequisites: [HAS_WEAPON, HAS_AMMO], effects: [ENEMY_SPOTTED], cost: 1 },
GET_AMMO => { name: GET_AMMO, prerequisites: [HAS_WEAPON], effects: [HAS_AMMO], cost: 2 },
GET_WEAPON => { name: GET_WEAPON, prerequisites: [], effects: [HAS_WEAPON], cost: 3 },
]
},
ALLY => {
name: ALLY,
tasks: [
ATTACK => { name: ATTACK, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_SPOTTED, WEAPONS_FREE], effects: [ENEMY_DEFEATED], cost: 0 },
REQUEST_ENGAGEMENT => { name: REQUEST_ENGAGEMENT, prerequisites: [HAS_WEAPON, HAS_AMMO, ENEMY_SPOTTED], effects: [WEAPONS_FREE], cost: 1 },
FOLLOW => { name: FOLLOW, prerequisites: [], effects: [ENEMY_SPOTTED], cost: 2 },
HOLD => { name: HOLD, prerequisites: [], effects: [ENEMY_SPOTTED], cost: 3 },
HUNT => { name: HUNT, prerequisites: [HAS_WEAPON, HAS_AMMO], effects: [ENEMY_SPOTTED], cost: 4 },
GET_AMMO => { name: GET_AMMO, prerequisites: [HAS_WEAPON], effects: [HAS_AMMO], cost: 5 },
GET_WEAPON => { name: GET_WEAPON, prerequisites: [], effects: [HAS_WEAPON], cost: 6 },
]
},
PLAYER => {
name: PLAYER,
tasks: [ FREE_PLAY => { name: FREE_PLAY, prerequisites: [], effects: [ENEMY_DEFEATED], cost: 0 } ]
}
];
}
interface IGoapAgent
{
public var job:GoapJob;
public var state:Map<EGoapState, Bool>;
public function set_state(state:EGoapState, bool:Bool):Void;
}
typedef GoapTask =
{
name:EGoapTask,
prerequisites:Array<EGoapState>,
effects:Array<EGoapState>,
cost:Int,
}
typedef GoapJob =
{
name: EGoapJob,
tasks: Map<EGoapTask, GoapTask>,
}
enum EGoapState
{
HAS_AMMO;
HAS_WEAPON;
ENEMY_DETECTED;
ENEMY_SPOTTED;
ENEMY_DEFEATED;
BACKUP_REQUESTED;
WEAPONS_FREE;
}
enum EGoapTask
{
PATROL;
HUNT;
HOLD;
FOLLOW;
ATTACK;
REQUEST_BACKUP;
REQUEST_ENGAGEMENT;
GET_WEAPON;
GET_AMMO;
FREE_PLAY;
}
enum EGoapJob
{
COMMS;
SOLDIER;
BACKUP;
ALLY;
PLAYER;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment