import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts"; import { Awaitable, log_from, Prototyped, Vec2 } from "../common/utils.ts"; import { ClassMap } from "../common/utils.ts"; import { wait } from "../common/utils.ts"; import { Constructible } from "../common/utils.ts"; import { first } from "../common/utils.ts"; const log = log_from(import.meta); export class Entity { identifier; components; engine; constructor(identifier: number, engine: Engine) { this.identifier = identifier; this.engine = engine; this.components = new ClassMap(); } insert(...components: Prototyped[]) { for (const c of components) this.components.insert(c); return this; } get(class_: Constructible) { return this.components.get(class_); } get_force(class_: Constructible) { const result = this.get(class_); if (result === null) throw new Error(); return result; } } export class Engine { next_identifier; entities; constructor() { this.next_identifier = 0; this.entities = new Map(); } start() { log("Started engine."); } spawn(op: (entity: Entity) => unknown) { const identifier = this.next_identifier++; const entity = new Entity(identifier, this); op(entity); this.entities.set(identifier, entity); return entity; } run(sys: (engine: Engine) => T) { return sys(this); } async global_system_loop(sys: (engine: Engine) => Awaitable, interval: number) { while (true) { await sys(this); await wait(interval); } } *query_all(query: Query) { yield* query.run(this.entities.values()); } query_one(query: Query) { return first(this.query_all(query)); } get(identifier: number) { const value = this.entities.get(identifier); if (value === undefined) return null; return value; } get_force(identifier: number) { const entity = this.get(identifier); if (entity === null) throw new Error(); return entity; } delete(identifier: number) { return this.entities.delete(identifier); } } function* displacement_steps(position: Vec2, displacement: Vec2) { let pos = position; let left = displacement; while (left.len() >= 1) { if (left.x() >= 1) { pos = pos.add(new Vec2(1, 0)); left = left.sub(new Vec2(1, 0)); } if (left.x() <= -1) { pos = pos.add(new Vec2(-1, 0)); left = left.sub(new Vec2(-1, 0)); } if (left.y() >= 1) { pos = pos.add(new Vec2(0, 1)); left = left.sub(new Vec2(0, 1)); } if (left.y() <= -1) { pos = pos.add(new Vec2(0, -1)); left = left.sub(new Vec2(0, -1)); } yield pos; } } Deno.test("test_displacement", () => { assertEquals( [...displacement_steps(new Vec2(1, 1), new Vec2(4, 6))], [new Vec2(2, 2), new Vec2(3, 3), new Vec2(4, 4), new Vec2(5, 5), new Vec2(5, 6), new Vec2(5, 7)], ); }); export class Query { private test; constructor(test: (entity: Entity) => boolean) { this.test = test; } and(query: Query) { return new Query((entity) => this.test(entity) && query.test(entity)); } static with(...components: Constructible[]) { return new Query((entity) => { for (const comp of components) if (!entity.components.has(comp)) return false; return true; }); } with(...components: Constructible[]) { return this.and(Query.with(...components)); } static filter(component: Constructible, filter: (comp: I, entity: Entity) => boolean) { return new Query((entity) => { const comp = entity.get(component); if (comp === null) return false; return filter(comp, entity); }); } filter(component: Constructible, filter: (comp: I, entity: Entity) => boolean) { return this.and(Query.filter(component, filter)); } *run(entities: Iterable) { for (const entity of entities) if (this.test(entity)) yield entity; } }