amatiasq
9/3/2019 - 9:01 AM

Lulas brainstorm

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));
  }
}