Skip to content

Commit

Permalink
Add types alias. Add more tests with few test scenarios.
Browse files Browse the repository at this point in the history
  • Loading branch information
Neloreck committed Nov 2, 2023
1 parent 156f39b commit 11221ab
Show file tree
Hide file tree
Showing 26 changed files with 403 additions and 127 deletions.
5 changes: 4 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
## List of todo tasks

- Add event emitter abstraction instead of interfaces + Java style
- Swap state machine execute condition - true finished / false not finished
- Add error handler
- Abstract property to interface?
- Add support of evaluators?
- Add support of planner as action (sub-planers)?
9 changes: 5 additions & 4 deletions src/AbstractAction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Properties } from "@/alias";
import { Property } from "@/Property";
import { PropertyId } from "@/types";
import { IUnit } from "@/unit/IUnit";
Expand All @@ -6,8 +7,8 @@ import { IUnit } from "@/unit/IUnit";
* Superclass for all actions a unit can perform
*/
export abstract class AbstractAction<T = any> {
private readonly preconditions: Array<Property> = [];
private readonly effects: Array<Property> = [];
private readonly preconditions: Properties = [];
private readonly effects: Properties = [];

public target: T;

Expand All @@ -21,7 +22,7 @@ export abstract class AbstractAction<T = any> {
/**
* @returns list of action preconditions
*/
public getPreconditions(): Array<Property> {
public getPreconditions(): Properties {
return this.preconditions;
}

Expand Down Expand Up @@ -57,7 +58,7 @@ export abstract class AbstractAction<T = any> {
/**
* @returns list of action effects
*/
public getEffects(): Array<Property> {
public getEffects(): Properties {
return this.effects;
}

Expand Down
6 changes: 3 additions & 3 deletions src/agent/AbstractAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { GenericAction, TestUnit } from "#/fixtures/mocks";

import { AbstractAction } from "@/AbstractAction";
import { AbstractAgent } from "@/agent/AbstractAgent";
import { Plan } from "@/alias";
import { GenericPlanner } from "@/planner/GenericPlanner";
import { IPlanner } from "@/planner/IPlanner";
import { Property } from "@/Property";
import { FiniteStateMachine } from "@/state_machine/FiniteStateMachine";
import { IdleState } from "@/state_machine/IdleState";
import { MoveToState } from "@/state_machine/MoveToState";
import { RunActionState } from "@/state_machine/RunActionState";
import { Queue } from "@/types";
import { IUnit } from "@/unit/IUnit";

describe("AbstractAgent class", () => {
Expand Down Expand Up @@ -119,7 +119,7 @@ describe("AbstractAgent class", () => {
const unit: TestUnit = new TestUnit();
const agent: Agent = new Agent(unit);
const state: MoveToState = new MoveToState(new GenericAction(55));
const plan: Queue<AbstractAction> = [new GenericAction(1), new GenericAction(2)];
const plan: Plan = [new GenericAction(1), new GenericAction(2)];

agent.fsm.push(state);
agent.fsm.push(agent.idleState);
Expand All @@ -135,7 +135,7 @@ describe("AbstractAgent class", () => {
it("should correctly handle plan failure", () => {
const unit: TestUnit = new TestUnit();
const agent: Agent = new Agent(unit);
const plan: Queue<AbstractAction> = [new GenericAction(1), new GenericAction(2)];
const plan: Plan = [new GenericAction(1), new GenericAction(2)];

agent.fsm.push(agent.idleState);

Expand Down
7 changes: 3 additions & 4 deletions src/agent/AbstractAgent.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { AbstractAction } from "@/AbstractAction";
import { IAgent } from "@/agent/IAgent";
import { Plan } from "@/alias";
import { IPlanner } from "@/planner/IPlanner";
import { Property } from "@/Property";
import { FiniteStateMachine } from "@/state_machine/FiniteStateMachine";
import { IdleState } from "@/state_machine/IdleState";
import { RunActionState } from "@/state_machine/RunActionState";
import { Queue } from "@/types";
import { AbstractUnit } from "@/unit/AbstractUnit";
import { IUnit } from "@/unit/IUnit";

Expand Down Expand Up @@ -94,7 +93,7 @@ export abstract class AbstractAgent implements IAgent {
*
* @param plan - newly created plan to work with
*/
public onPlanCreated(plan: Queue<AbstractAction>): void {
public onPlanCreated(plan: Plan): void {
this.unit.onGoapPlanFound(plan);

this.fsm.pop();
Expand All @@ -106,7 +105,7 @@ export abstract class AbstractAgent implements IAgent {
*
* @param plan - remaining actions in the plan after failure
*/
public onPlanFailed(plan: Queue<AbstractAction>): void {
public onPlanFailed(plan: Plan): void {
this.unit.onGoapPlanFailed(plan);
}

Expand Down
13 changes: 13 additions & 0 deletions src/alias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AbstractAction } from "@/AbstractAction";
import { Property } from "@/Property";
import { Queue } from "@/types";

/**
* List of properties.
*/
export type Properties = Array<Property>;

/**
* Generic actions plan queue.
*/
export type Plan = Queue<AbstractAction>;
5 changes: 2 additions & 3 deletions src/event/IFiniteStateMachinePlanEventListener.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { AbstractAction } from "@/AbstractAction";
import { Queue } from "@/types";
import { Plan } from "@/alias";

export interface IFiniteStateMachinePlanEventListener {
/**
* Gets called when a RunActionState on the FSM throws an exception.
*
* @param plan - the rest of the action queue which failed to execute
*/
onPlanFailed(plan: Queue<AbstractAction>): void;
onPlanFailed(plan: Plan): void;
/**
* Gets called when a RunActionState on the FSM returns true and therefore signals that it is finished.
*/
Expand Down
5 changes: 2 additions & 3 deletions src/event/IPlanCreatedEventListener.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { AbstractAction } from "@/AbstractAction";
import { Queue } from "@/types";
import { Plan } from "@/alias";

export interface IPlanCreatedEventListener {
/**
Expand All @@ -8,5 +7,5 @@ export interface IPlanCreatedEventListener {
*
* @param plan - the plan that the planner has created and is ready to be executed
*/
onPlanCreated(plan: Queue<AbstractAction>): void;
onPlanCreated(plan: Plan): void;
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "@/alias";
export * from "@/agent";
export * from "@/error";
export * from "@/event";
Expand Down
13 changes: 6 additions & 7 deletions src/planner/AbstractPlanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { describe, expect, it, jest } from "@jest/globals";

import { GenericAction, TestUnit } from "#/fixtures/mocks";

import { AbstractAction } from "@/AbstractAction";
import { Plan } from "@/alias";
import { DirectedWeightedGraph, IWeightedEdge, IWeightedGraph } from "@/graph";
import { AbstractPlanner } from "@/planner/AbstractPlanner";
import { GraphNode } from "@/planner/GraphNode";
import { Property } from "@/Property";
import { Queue } from "@/types";
import { AbstractUnit } from "@/unit/AbstractUnit";

describe("GenericPlanner class", () => {
Expand Down Expand Up @@ -62,7 +61,7 @@ describe("GenericPlanner class", () => {
expect(planner.getStartNode()).toBeUndefined();
expect(planner.getEndNodes()).toBeUndefined();

const plan: Queue<AbstractAction> = planner.plan(unit) as Queue<AbstractAction>;
const plan: Plan = planner.plan(unit) as Plan;
const startNode: GraphNode = planner.getStartNode();
const endNodes: Array<GraphNode> = planner.getEndNodes();

Expand Down Expand Up @@ -103,7 +102,7 @@ describe("GenericPlanner class", () => {

unit.addAction(action);

const plan: Queue<AbstractAction> = new Planner().plan(unit) as Queue<AbstractAction>;
const plan: Plan = new Planner().plan(unit) as Plan;

expect(plan).not.toBeNull();
expect(plan).toHaveLength(1);
Expand Down Expand Up @@ -133,7 +132,7 @@ describe("GenericPlanner class", () => {
unit.addAction(new GenericAction(1));

const planner: Planner = new Planner();
const plan: Queue<AbstractAction> = planner.plan(unit) as Queue<AbstractAction>;
const plan: Plan = planner.plan(unit) as Plan;

expect(plan).not.toBeNull();
expect(plan).toHaveLength(2);
Expand Down Expand Up @@ -166,7 +165,7 @@ describe("GenericPlanner class", () => {
jest.spyOn(firstExpensive, "generateBaseCost").mockImplementation(() => 100);
jest.spyOn(firstCheap, "generateBaseCost").mockImplementation(() => 10);

const plan: Queue<AbstractAction> = new Planner().plan(unit) as Queue<AbstractAction>;
const plan: Plan = new Planner().plan(unit) as Plan;

expect(plan).not.toBeNull();
expect(plan).toHaveLength(2);
Expand Down Expand Up @@ -206,7 +205,7 @@ describe("GenericPlanner class", () => {
unit.addAction(collectWoodAction);
unit.addAction(getAxeAction);

const plan: Queue<AbstractAction> = new Planner().plan(unit) as Queue<AbstractAction>;
const plan: Plan = new Planner().plan(unit) as Plan;

expect(plan).not.toBeNull();
expect(plan).toHaveLength(4);
Expand Down
28 changes: 13 additions & 15 deletions src/planner/AbstractPlanner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IWeightedEdge, IWeightedGraph } from "src/graph";

import { AbstractAction } from "@/AbstractAction";
import { Plan, Properties } from "@/alias";
import { IWeightedPath } from "@/graph/IWeightedPath";
import { GraphNode } from "@/planner/GraphNode";
import { Property } from "@/Property";
Expand Down Expand Up @@ -59,9 +60,7 @@ export abstract class AbstractPlanner {
this.endNodes = [];

try {
const goal: Array<Property> = this.unit
.getGoalState()
.sort((first, second) => first.importance - second.importance);
const goal: Properties = this.unit.getGoalState().sort((first, second) => second.importance - first.importance);

// The Integer.MaxValue indicates that the goal was passed by the changeGoalImmediatly function.
// An empty Queue is returned instead of null because null would result in the IdleState to call this
Expand Down Expand Up @@ -91,7 +90,7 @@ export abstract class AbstractPlanner {
* @param goalState - list of states the action queue has to fulfill
* @returns a DirectedWeightedGraph generated from all possible unit actions for a goal
*/
protected createGraph(goalState: Array<Property> = this.unit.getGoalState()): IWeightedGraph<GraphNode> {
protected createGraph(goalState: Properties = this.unit.getGoalState()): IWeightedGraph<GraphNode> {
const generatedGraph: IWeightedGraph<GraphNode> = this.createBaseGraph();

this.addVertices(generatedGraph, goalState);
Expand All @@ -106,7 +105,7 @@ export abstract class AbstractPlanner {
* @param graph - the graph the vertices are being added to
* @param goalState - list of States the unit has listed as their goals
*/
protected addVertices(graph: IWeightedGraph<GraphNode>, goalState: Array<Property>): void {
protected addVertices(graph: IWeightedGraph<GraphNode>, goalState: Properties): void {
// The effects from the world state as well as the precondition of each
// goal have to be set at the beginning, since these are the effects the
// unit tries to archive with its actions. Also the startNode has to
Expand Down Expand Up @@ -239,7 +238,7 @@ export abstract class AbstractPlanner {
// produce a suitable effect set regarding the preconditions of
// the current node.
for (const pathToListNode of node.pathsToThisNode) {
if (areAllPreconditionsMet(otherNodeInGraph.preconditions, node.getEffectState(pathToListNode))) {
if (areAllPreconditionsMet(otherNodeInGraph.preconditions, node.states.get(pathToListNode) as Properties)) {
connected = true;

graph.addEdge(node, otherNodeInGraph, { weight: (node.action as AbstractAction).generateCost(this.unit) });
Expand All @@ -264,20 +263,19 @@ export abstract class AbstractPlanner {
}

/**
* Function for searching a graph for the lowest cost of a series of actions
* which have to be taken to archive a certain goal which has most certainly
* the highest importance.
* Function for searching a graph for the lowest cost of a series of actions which have to be taken to archive
* a certain goal which has most certainly the highest importance.
*
* @param graph - the graph of goap actions the unit has to take in order to achieve a goal
* @returns the Queue of goap actions which has the lowest cost to archive a goal
*/
protected searchGraphForActionQueue(graph: IWeightedGraph<GraphNode>): Optional<Queue<AbstractAction>> {
for (let i = 0; i < this.endNodes.length; i++) {
this.endNodes[i].pathsToThisNode.sort((first, second) => first.totalWeight - second.totalWeight);
protected searchGraphForActionQueue(graph: IWeightedGraph<GraphNode>): Optional<Plan> {
if (this.endNodes.length) {
// Sort by cost to find the most efficient way.
this.endNodes[0].pathsToThisNode.sort((first, second) => first.totalWeight - second.totalWeight);

for (let j = 0; j < this.endNodes[i].pathsToThisNode.length; j++) {
return extractActionsFromGraphPath(this.endNodes[i].pathsToThisNode[j], this.startNode, this.endNodes[i]);
}
// Get the most efficient for the most important goal.
return extractActionsFromGraphPath(this.endNodes[0].pathsToThisNode[0], this.startNode, this.endNodes[0]) ?? null;
}

return null;
Expand Down
Loading

0 comments on commit 11221ab

Please sign in to comment.