const a = new Life();
a.pos = new Vector(10, 10);
a.setParameter(Parameter.ENERGY, 100);
a.setParameter(Parameter.WEIGHT, 1);
a.setParameter(Parameter.FOOD, x => x.hasParameter(Parameter.IS_PLANT));
a.addSense(new Sight({ radius: 10, angle: 270, precision: 1 }))
a.addSense(new Hearing({ radius: 1, angle: 360, })
a.addBehaviour(new FlockingBehaviour(), { intensity: 0.5 });
a.addBehaviour(new PredatorBehaviour(), { intensity: 1 });
a.addBehaviour(new PeopleBehaviour(), { intensity: 0.2 });
a.tick(world);
type ConstructorOf<T> = new() => T;
interface IBehaviour {
tick(event: TickEvent);
reset(); // kind of Tick destructor, to remove any cache for the tick
}
interface ISense {
sense(at: IVector, world: IWorld): Life[];
}
interface IVector {
readonly x: number;
readonly y: number;
multiply(value: number): IVector;
merge(other: IVector): IVector;
}
interface IWorld {
getEntitiesAt(center: IVector, radius: number): Life[];
}
class Life {
pos: IVector;
acceleration: IVector;
private readonly parameters = new Map<Parameter, any>();
private readonly senses: ISense[] = [];
private readonly behaviours = new Map();
setParameter(param: Parameter, value: any) {
this.parameters.set(param, value);
}
getParameter<T>(param: Parameter) {
return this.parameters.get(param) as T;
}
addSense(sense: ISense) {
this.senses.push(sense);
}
getSenses() {
return this.senses;
}
addBehaviour(behaviour: IBehaviour, { intensity }: { intensity: number } = { intensity: 1 }) {
this.behaviours.set(behaviour.constructor, {
implementation: behaviour,
intensity
});
}
getBehaviour<T>(Class: ConstructorOf<T>): T {
const entry = this.behaviours.get(Class);
return entry && entry.implementation;
}
shove(force) {
this.acceleration = this.acceleration.merge(force);
}
tick(world: IWorld) {
// Can be cached
const event = new TickEvent(this, world);
this.behaviours.forEach(({ implementation, intensity }) => {
event.intensity = intensity;
implementation.tick(event);
});
const force = event.getWillForce();
if (force) {
this.shove(force);
}
}
reset() {
this.behaviours.forEach(({ implementation }) => implementation.reset());
}
}
enum Parameter {
ENERGY,
WEIGHT,
FOOD,
IS_PLANT,
}
class FlockingBehaviour implements IBehaviour {
private _isFlocking = false;
get isFlocking() {
return this._isFlocking;
}
tick(event: TickEvent) {
const neighbors = event.useSenses();
const flockings = neighbors
.map(x => x.getBehaviour(FlockingBehaviour))
.filter(Boolean);
if (flockings.some(x => x.isFlocking)) {
this._isFlocking = true;
...
event.setWill(flockingDirection);
}
}
reset() {
this._isFlocking = false;
}
}
class TickEvent {
intensity = 1;
private readonly pushes: IVector[] = [];
constructor(
private readonly self: Life,
private readonly world: IWorld
) {}
useSenses(): Life[] {
const senses = this.self.getSenses();
return senses.flatMap(x => x.sense(this.self.pos, this.world));
}
setWill(force: IVector) {
this.pushes.push(force.multiply(this.intensity));
}
getWillForce() {
return this.pushes.reduce((a, b) => a.merge(b));
}
}