Skip to content

Commit

Permalink
Work in progress - rewriting AI mechanism.
Browse files Browse the repository at this point in the history
  • Loading branch information
dom111 committed May 6, 2023
1 parent ec5c64c commit b643432
Show file tree
Hide file tree
Showing 38 changed files with 469 additions and 528 deletions.
27 changes: 7 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# core-strategy

A framework for building modular AI. The idea being that `Strategy`s are built of many `Routine`s for specific
`PlayerAction`s and it should be easy enough to register new `Strategy`s to handle newly added `PlayerAction`s.
A framework for building modular AI. The idea being that `Strategy`s can handle one of many `PlayerAction`s and it
should be easy enough to register new `Strategy`s to handle newly added `PlayerAction`s or specific custom `Unit`s and
their actions (`Caravan`, `Diplomat`, etc).

The consumer `AIClient`, `StrategyAIClient`, is available at
[civ-clone/core-strategy-ai-client](https://github.com/civ-clone/core-strategy-ai-client).
Expand All @@ -12,27 +13,13 @@ Lastly, there's the possibility for primitive Barbarian behaviour, without havin

## Current state

This is purely illustrative and might change quite substantially until I've got a reasonable working implementation.

```ts
import Strategy from '@civ-clone/core-strategy/Strategy';

// Export your base `Strategy`:
export class MyStrategy extends Strategy {
constructor(
dependencyForRoutine: SpecificThing = defaultInstanceOfSpecificThing
) {
super(
new MyRoutine(dependencyForRoutine),
new AnotherRoutine(dependencyForRoutine)
);
}
}

import PlayerAction from '@civ-clone/core-player/PlayerAction';
import Routine from '@civ-clone/core-strategy/Strategy';
import { instance as strategyNoteInstance } from '@civ-clone/core-strategy/StrategyNoteRegistry';

// which is comprised of `Routine`s like the following:
export class MyRoutine extends Routine {
// Inject dependencies (e.g. `Registry`s) into the `constructor` as usual

attempt(action: PlayerAction<SpecificItemClass>): boolean {
Expand Down Expand Up @@ -69,7 +56,7 @@ export class MyRoutine extends Routine {
import { generateKey } from '@civ-clone/core-strategy/StrategyNote';

export const myCustomKeyGenerator = (item: SpecificItemClass) =>
generateKey(item, MyRoutine.name);
generateKey(item, MyStrategy.name);

// To control the priority of your `Routine`s you need to use `Priority` `Rule`s and you can even take the `Leader`s
// `Trait`s into consideration if you wish:
Expand All @@ -89,7 +76,7 @@ export const getRules = (
): Priority[] => [
new Priority(
new Criterion(
(player: Player, routine: Routine) => routine instanceof MyRoutine
(player: Player, strategy: Strategy) => routine instanceof MyRoutine
),
new Effect((player: Player) => {
const civilization = player.civilization(),
Expand Down
15 changes: 0 additions & 15 deletions Routine.d.ts

This file was deleted.

38 changes: 0 additions & 38 deletions Routine.js

This file was deleted.

1 change: 0 additions & 1 deletion Routine.js.map

This file was deleted.

39 changes: 0 additions & 39 deletions Routine.ts

This file was deleted.

9 changes: 6 additions & 3 deletions Rules/Priority.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Player from '@civ-clone/core-player/Player';
import PlayerAction from '@civ-clone/core-player/PlayerAction';
import PriorityValue from '@civ-clone/core-rule/Priority';
import Routine from '../Routine';
import Rule from '@civ-clone/core-rule/Rule';
export declare class Priority extends Rule<[Player, Routine], PriorityValue> {}
import Strategy from '../Strategy';
export declare class Priority extends Rule<
[PlayerAction, Strategy],
PriorityValue
> {}
export default Priority;
2 changes: 1 addition & 1 deletion Rules/Priority.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Rules/Priority.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Player from '@civ-clone/core-player/Player';
import PlayerAction from '@civ-clone/core-player/PlayerAction';
import PriorityValue from '@civ-clone/core-rule/Priority';
import Routine from '../Routine';
import Rule from '@civ-clone/core-rule/Rule';
import Strategy from '../Strategy';

export class Priority extends Rule<[Player, Routine], PriorityValue> {}
export class Priority extends Rule<[PlayerAction, Strategy], PriorityValue> {}

export default Priority;
10 changes: 5 additions & 5 deletions Strategy.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import Player from '@civ-clone/core-player/Player';
import { RuleRegistry } from '@civ-clone/core-rule/RuleRegistry';
import PlayerAction from '@civ-clone/core-player/PlayerAction';
import Priority from '@civ-clone/core-rule/Priority';
import Routine from './Routine';
export interface IStrategy {
attempt(action: PlayerAction): boolean;
priority(player: Player): Priority;
priority(action: PlayerAction): Priority;
}
export declare class Strategy implements IStrategy {
#private;
constructor(...items: Routine[]);
constructor(ruleRegistry?: RuleRegistry);
/**
* Checks to see if the `action` can be handled, returns `true` if it is, `false` otherwise.
*/
attempt(action: PlayerAction): boolean;
priority(player: Player): Priority;
priority(action: PlayerAction): Priority;
protected ruleRegistry(): RuleRegistry;
}
export default Strategy;
34 changes: 23 additions & 11 deletions Strategy.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Strategy.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 19 additions & 15 deletions Strategy.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
import Player from '@civ-clone/core-player/Player';
import {
RuleRegistry,
instance as ruleRegistryInstance,
} from '@civ-clone/core-rule/RuleRegistry';
import PlayerAction from '@civ-clone/core-player/PlayerAction';
import Priority from '@civ-clone/core-rule/Priority';
import Routine from './Routine';
import PriorityRule from './Rules/Priority';

export interface IStrategy {
attempt(action: PlayerAction): boolean;
priority(player: Player): Priority;
priority(action: PlayerAction): Priority;
}

export class Strategy implements IStrategy {
#routines: Routine[] = [];
#ruleRegistry: RuleRegistry;

constructor(...items: Routine[]) {
items.forEach((item) => this.#routines.push(item));
constructor(ruleRegistry: RuleRegistry = ruleRegistryInstance) {
this.#ruleRegistry = ruleRegistry;
}

/**
* Checks to see if the `action` can be handled, returns `true` if it is, `false` otherwise.
*/
attempt(action: PlayerAction): boolean {
return this.#routines
.sort(
(a, b) =>
a.priority(action.player()).value() -
b.priority(action.player()).value()
)
.some((routine) => routine.attempt(action));
throw new Error('This must be overwritten in the implementor.');
}

priority(player: Player): Priority {
priority(action: PlayerAction): Priority {
return new Priority(
// This takes the highest priority (lowest value) from all the applicable `PriorityRule`s
Math.min(
...this.#routines.map((routine) => routine.priority(player).value()),
...this.#ruleRegistry
.process(PriorityRule, action, this)
.map((priority) => priority.value()),
Infinity
)
);
}

protected ruleRegistry(): RuleRegistry {
return this.#ruleRegistry;
}
}

export default Strategy;
6 changes: 2 additions & 4 deletions StrategyNote.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
declare type IDataObject = {
id(): string;
};
import { IDataObject } from '@civ-clone/core-data-object/DataObject';
export interface IStrategyNote<Value = any> {
key(): string;
value(): Value;
Expand All @@ -12,6 +10,6 @@ export declare class StrategyNote<Value = any> implements IStrategyNote<Value> {
value(): Value;
}
export declare const generateKey: (
...items: (IDataObject | string)[]
...items: (Pick<IDataObject, 'id'> | string)[]
) => string;
export default StrategyNote;
2 changes: 1 addition & 1 deletion StrategyNote.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion StrategyNote.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 7 additions & 8 deletions StrategyNote.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import DataObject from '@civ-clone/core-data-object/DataObject';

type IDataObject = {
id(): string;
};
import {
DataObject,
IDataObject,
} from '@civ-clone/core-data-object/DataObject';

export interface IStrategyNote<Value = any> {
key(): string;
Expand All @@ -27,9 +26,9 @@ export class StrategyNote<Value = any> implements IStrategyNote<Value> {
}
}

export const generateKey: (...items: (IDataObject | string)[]) => string = (
...items: (IDataObject | string)[]
) =>
export const generateKey: (
...items: (Pick<IDataObject, 'id'> | string)[]
) => string = (...items: (Pick<IDataObject, 'id'> | string)[]) =>
items
.map((item) =>
item instanceof DataObject
Expand Down
1 change: 1 addition & 0 deletions StrategyNoteRegistry.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export declare class StrategyNoteRegistry
extends EntityRegistry<StrategyNote>
implements IStrategyNoteRegistry
{
constructor();
getByKey<Value = any>(key: string): StrategyNote<Value> | undefined;
getOrCreateByKey<Value>(key: string, value: Value): StrategyNote<Value>;
register(...entities: StrategyNote[]): void;
Expand Down
Loading

0 comments on commit b643432

Please sign in to comment.