From cbb88091bdf69cc8752ef1cc3662dc0b99e3ead6 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 2 Mar 2024 05:30:17 -0700 Subject: [PATCH] key lock / player curry collisions --- public/assets/curry.png | Bin 0 -> 1839 bytes src/engine/Game.ts | 4 ++ src/engine/TheAbstractionEngine.ts | 21 +++++- src/engine/config/sprites.ts | 30 +++++++++ src/engine/entities/Curry.ts | 45 +++++++++++++ src/engine/entities/EntityNames.ts | 3 + src/engine/entities/Key.ts | 45 +++++++++++++ src/engine/entities/LockedDoor.ts | 45 +++++++++++++ src/engine/entities/index.ts | 3 + src/engine/systems/Collision.ts | 103 +++++++++++++++++++++++++++++ src/engine/systems/Grid.ts | 36 +++++++--- src/engine/systems/SystemNames.ts | 1 + src/engine/systems/index.ts | 1 + 13 files changed, 325 insertions(+), 12 deletions(-) create mode 100644 public/assets/curry.png create mode 100644 src/engine/entities/Curry.ts create mode 100644 src/engine/entities/Key.ts create mode 100644 src/engine/entities/LockedDoor.ts create mode 100644 src/engine/systems/Collision.ts diff --git a/public/assets/curry.png b/public/assets/curry.png new file mode 100644 index 0000000000000000000000000000000000000000..348063c583c1ec25069a75d50d4b438c8e6b87c7 GIT binary patch literal 1839 zcmV+~2hjM5P)A|aW;O)bozrcoHk)ZE09B`7=+fy6M(5)w3G2?B#) z6zJ&fZTmcL+qbvBKF4=@+CIPDxA&a;g$sxMvl&>JfeQ;`-_SP$haG@zf~6Ty55Ql) z-G6TwzWMVn(Y5RttAriP+}o>q?A~4MF^<@|dgTJ`3vr-{Uo`*-m?6eDzZ(|jenjrM zuCt;s7@0BW!im;JJj9BhJ1?38?ICqtEdUZ=e&ufkzJxe*uM|JqAyebfo?2gLf-4!L zfn6m4Tl`N}Lp-pRjWJ1Ub2IBP`$ilvb1-{}jZqH(I@d}*A81s767raPQvyoFM?5im zX*~wv^NWP9FDf84@Tdl$R$FI?tU-p>9I^eUaJ3L$3$LA*soqS1&}7*n62EPZj07fgh*-uUNabM`}e!y3@-s)UYR0*I)G;P z?;-%H3aAObRRGY_D+d3F1);?J%rUu!erf?=Dqw01bAJs2wB}&M_q*gwpMx0vYKLw-#?GrA0Onl)IwEM3u;*Ww0hIu(OQ#I^ zjcXS_ep6QK=i&3W&GoH~>F%vNC-nUK$Gwx^=-rcD0gJ1xV=_S?LEY7s; zkRezCm;i*t4;=$i;@deSCTM|QVgR!r*91%eT)ku1C0a4zH)Gg28T_&CcN73g)e8|J zsn`q&l8VBR;ET@&zz_Vn0+3rsV&V7khxBKUIILM+-H~SQoH^LhX9E-dWB^j&TLgd! ziDXRb42-&-T?ZLLY6N5g3BK6alo8wxASr*G?6i5ME?@_Z}DkR&;k#DG{sz zq(u1S#E7v5U<2UJ%N_+&)eGUz#myQa7J#eY9G{N=etr7N!pAS^*ct&~F}s~F*a3X- z<_4DlR0Snf3mJepcjr_-dk<>>p96o*Tuu;vHUJXw{VHhxZHoZXKgM@12NJ)OR()Uh ztN<`okOhSdz|P~2lb9hCfRF)D08;xbHHHL_lrI*4r~^pXGUKIOXkib&2phEm7-DmY zVhvCdfE=T=x(DH}W&Bon*mdXt)FeQfegr1h^r7RA`CY64AP(G)fyN?Ct*_Szz=-&x zUxhFxU_JmDp+*EAJOAzggbM%@=+S)OhW_M5*bcyPu$iOJXaXF5RXK3d-o12Dt!29X;3E>O<<0JK)G*H`4g$DH+Dpk4)K z_f`50NOqmx$L#3MS$u6?h_G~(2>UN0N_7{nY92e8-Rb` zzWYC;k6*q^{vE`u;-?+~hYHwiJcJ5g)CeX|6c#xXk;F>Z3IYgvFJlZ)phF^00^ZDqRgA%+j0HdVe3Rc_vv*0f=zvZ^a z5R{mo>Hs~1cdJw>5$G7#qxj5`$Bg;<;#bsT&(S!jQS7J=(0d59&A;&r6cM$mUhik&8dVTg{$8GtrS#4{;$;P(hjZ~c%->T1s96Pb0-((WGP}QL=Iwq6 zXLbOz35bb;h|#M2y>tSEKi3E)A;|Fp!olvR6mM|=T66=3tX9Tv83YEm-A}0zSi`a7 z(B}jRwp!I*r31K=BG4>(5^{PGz684jucjGTeM-j}t|XW>HLlVDN~)rSh>^sX1R{q6 zO;eN_e5CtT0wC!MQt?L=9)*G7U_{&_1VKFjC=5va*@gGR!F*4D1Y?b|$EE=og_OO| zbr{$ISV!xPG{C?Pz_!KG4D0|bE&m3;1F$`?Gy^*TOUu8(?*MEMEX}|Uz|!(>@H+t8 d14}dT9~=3IEqAySQ2+n{07*qoM6N<$f&h>XSLXl# literal 0 HcmV?d00001 diff --git a/src/engine/Game.ts b/src/engine/Game.ts index 2df9f17..9fe9e87 100644 --- a/src/engine/Game.ts +++ b/src/engine/Game.ts @@ -25,6 +25,10 @@ export class Game { this.running = true; } + public stop() { + this.running = false; + } + public addEntity(entity: Entity) { this.entities.set(entity.id, entity); } diff --git a/src/engine/TheAbstractionEngine.ts b/src/engine/TheAbstractionEngine.ts index 7f71fd5..e84093f 100644 --- a/src/engine/TheAbstractionEngine.ts +++ b/src/engine/TheAbstractionEngine.ts @@ -1,12 +1,21 @@ import { Game } from "."; import { Miscellaneous, loadAssets } from "./config"; -import { Player, FunctionBox, Wall, LambdaFactory } from "./entities"; +import { + Player, + FunctionBox, + Wall, + LambdaFactory, + Key, + LockedDoor, + Curry, +} from "./entities"; import { Grid, FacingDirection, Input, Render, LambdaFactory as LambdaFactorySpawnSystem, + Collision, } from "./systems"; export class TheAbstractionEngine { @@ -39,6 +48,7 @@ export class TheAbstractionEngine { }, ), new LambdaFactorySpawnSystem(), + new Collision(), new Render(this.ctx), ].forEach((system) => this.game.addSystem(system)); @@ -55,6 +65,15 @@ export class TheAbstractionEngine { const factory = new LambdaFactory({ x: 6, y: 6 }, "λ x . (x)", 10); this.game.addEntity(factory); + + const lockedDoor = new LockedDoor({ x: 8, y: 8 }); + this.game.addEntity(lockedDoor); + + const key = new Key({ x: 7, y: 7 }); + this.game.addEntity(key); + + const curry = new Curry({ x: 9, y: 8 }); + this.game.addEntity(curry); } public play() { diff --git a/src/engine/config/sprites.ts b/src/engine/config/sprites.ts index 39ad260..cca5961 100644 --- a/src/engine/config/sprites.ts +++ b/src/engine/config/sprites.ts @@ -5,6 +5,9 @@ export enum Sprites { FUNCTION_BOX, WALL, LAMBDA_FACTORY, + KEY, + LOCKED_DOOR, + CURRY, } export interface SpriteSpec { @@ -66,3 +69,30 @@ const lambdaFactorySpriteSpec = { sheet: "/assets/function_factory.png", }; SPRITE_SPECS.set(Sprites.LAMBDA_FACTORY, lambdaFactorySpriteSpec); + +const keySpriteSpec = { + msPerFrame: 200, + width: 64, + height: 64, + frames: 3, + sheet: "/assets/key.png", +}; +SPRITE_SPECS.set(Sprites.KEY, keySpriteSpec); + +const lockedDoorSpriteSpec = { + msPerFrame: 200, + width: 64, + height: 64, + frames: 3, + sheet: "/assets/locked_door.png", +}; +SPRITE_SPECS.set(Sprites.LOCKED_DOOR, lockedDoorSpriteSpec); + +const currySpriteSpec = { + msPerFrame: 200, + width: 64, + height: 64, + frames: 3, + sheet: "/assets/curry.png", +}; +SPRITE_SPECS.set(Sprites.CURRY, currySpriteSpec); diff --git a/src/engine/entities/Curry.ts b/src/engine/entities/Curry.ts new file mode 100644 index 0000000..85bc7ef --- /dev/null +++ b/src/engine/entities/Curry.ts @@ -0,0 +1,45 @@ +import { Entity, EntityNames } from "."; +import { BoundingBox, Colliding, Grid, Sprite } from "../components"; +import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config"; +import { Coord2D } from "../interfaces"; + +export class Curry extends Entity { + private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( + Sprites.CURRY, + ) as SpriteSpec; + + constructor(gridPosition: Coord2D) { + super(EntityNames.Curry); + + this.addComponent(new Grid(gridPosition)); + + this.addComponent(new Colliding()); + + this.addComponent( + new BoundingBox( + { + x: 0, + y: 0, + }, + { + width: Curry.spriteSpec.width, + height: Curry.spriteSpec.height, + }, + 0, + ), + ); + + this.addComponent( + new Sprite( + IMAGES.get(Curry.spriteSpec.sheet)!, + { x: 0, y: 0 }, + { + width: Curry.spriteSpec.width, + height: Curry.spriteSpec.height, + }, + Curry.spriteSpec.msPerFrame, + Curry.spriteSpec.frames, + ), + ); + } +} diff --git a/src/engine/entities/EntityNames.ts b/src/engine/entities/EntityNames.ts index 3ad31d0..7fc69c6 100644 --- a/src/engine/entities/EntityNames.ts +++ b/src/engine/entities/EntityNames.ts @@ -3,4 +3,7 @@ export namespace EntityNames { export const FunctionBox = "FunctionBox"; export const Wall = "Wall"; export const LambdaFactory = "LambdaFactory"; + export const Key = "Key"; + export const LockedDoor = "LockedDoor"; + export const Curry = "Curry"; } diff --git a/src/engine/entities/Key.ts b/src/engine/entities/Key.ts new file mode 100644 index 0000000..7168ee8 --- /dev/null +++ b/src/engine/entities/Key.ts @@ -0,0 +1,45 @@ +import { Entity, EntityNames } from "."; +import { BoundingBox, Grid, Pushable, Sprite } from "../components"; +import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config"; +import { Coord2D } from "../interfaces"; + +export class Key extends Entity { + private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( + Sprites.KEY, + ) as SpriteSpec; + + constructor(gridPosition: Coord2D) { + super(EntityNames.Key); + + this.addComponent(new Grid(gridPosition)); + + this.addComponent(new Pushable()); + + this.addComponent( + new BoundingBox( + { + x: 0, + y: 0, + }, + { + width: Key.spriteSpec.width, + height: Key.spriteSpec.height, + }, + 0, + ), + ); + + this.addComponent( + new Sprite( + IMAGES.get(Key.spriteSpec.sheet)!, + { x: 0, y: 0 }, + { + width: Key.spriteSpec.width, + height: Key.spriteSpec.height, + }, + Key.spriteSpec.msPerFrame, + Key.spriteSpec.frames, + ), + ); + } +} diff --git a/src/engine/entities/LockedDoor.ts b/src/engine/entities/LockedDoor.ts new file mode 100644 index 0000000..5e364b8 --- /dev/null +++ b/src/engine/entities/LockedDoor.ts @@ -0,0 +1,45 @@ +import { Entity, EntityNames } from "."; +import { BoundingBox, Colliding, Grid, Sprite } from "../components"; +import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config"; +import { Coord2D } from "../interfaces"; + +export class LockedDoor extends Entity { + private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( + Sprites.LOCKED_DOOR, + ) as SpriteSpec; + + constructor(gridPosition: Coord2D) { + super(EntityNames.LockedDoor); + + this.addComponent(new Grid(gridPosition)); + + this.addComponent(new Colliding()); + + this.addComponent( + new BoundingBox( + { + x: 0, + y: 0, + }, + { + width: LockedDoor.spriteSpec.width, + height: LockedDoor.spriteSpec.height, + }, + 0, + ), + ); + + this.addComponent( + new Sprite( + IMAGES.get(LockedDoor.spriteSpec.sheet)!, + { x: 0, y: 0 }, + { + width: LockedDoor.spriteSpec.width, + height: LockedDoor.spriteSpec.height, + }, + LockedDoor.spriteSpec.msPerFrame, + LockedDoor.spriteSpec.frames, + ), + ); + } +} diff --git a/src/engine/entities/index.ts b/src/engine/entities/index.ts index a049350..45c95c0 100644 --- a/src/engine/entities/index.ts +++ b/src/engine/entities/index.ts @@ -4,3 +4,6 @@ export * from "./Player"; export * from "./FunctionBox"; export * from "./Wall"; export * from "./LambdaFactory"; +export * from "./Key"; +export * from "./LockedDoor"; +export * from "./Curry"; diff --git a/src/engine/systems/Collision.ts b/src/engine/systems/Collision.ts new file mode 100644 index 0000000..7b1b963 --- /dev/null +++ b/src/engine/systems/Collision.ts @@ -0,0 +1,103 @@ +import { System, SystemNames } from "."; +import { Game } from ".."; +import { Entity, EntityNames } from "../entities"; +import { BoundingBox, Colliding, ComponentNames, Grid } from "../components"; + +const collisionMap: Record> = { + [EntityNames.Key]: new Set([EntityNames.LockedDoor]), + [EntityNames.Curry]: new Set([EntityNames.Player]), +}; + +export class Collision extends System { + static canCollide(entityName: string, otherEntityName: string) { + if (collisionMap[entityName]) { + return collisionMap[entityName].has(otherEntityName); + } + return collisionMap[otherEntityName]?.has(entityName) ?? false; + } + + constructor() { + super(SystemNames.Collision); + } + + public update(_dt: number, game: Game) { + game.forEachEntityWithComponent(ComponentNames.Colliding, (entity) => { + if (!entity.hasComponent(ComponentNames.BoundingBox)) { + return; + } + const collidingBox = entity.getComponent( + ComponentNames.BoundingBox, + ); + let collidingGrid = entity.hasComponent(ComponentNames.Grid) + ? entity.getComponent(ComponentNames.Grid) + : null; + + const collidingWith: Entity[] = []; + game.forEachEntityWithComponent( + ComponentNames.BoundingBox, + (otherEntity) => { + const otherBoundingBox = otherEntity.getComponent( + ComponentNames.BoundingBox, + ); + let otherGrid = otherEntity.hasComponent(ComponentNames.Grid) + ? otherEntity.getComponent(ComponentNames.Grid) + : null; + + if (collidingGrid && otherGrid) { + if ( + collidingGrid.gridPosition.x === otherGrid.gridPosition.x && + collidingGrid.gridPosition.y === otherGrid.gridPosition.y + ) { + collidingWith.push(otherEntity); + } + return; + } + + if (collidingBox.isCollidingWith(otherBoundingBox)) { + collidingWith.push(otherEntity); + } + }, + ); + + for (const collision of collidingWith) { + this.handleCollision(entity, collision, game); + } + }); + } + + private handleCollision(entity: Entity, otherEntity: Entity, game: Game) { + if (!Collision.canCollide(entity.name, otherEntity.name)) { + return; + } + + const keyDoorPair = [EntityNames.Key, EntityNames.LockedDoor].map((x) => + [entity, otherEntity].find((y) => y.name === x), + ); + const [key, door] = keyDoorPair; + if (key && door) { + this.handleKeyDoorCollision(key, door, game); + } + + const curryPlayerPair = [EntityNames.Curry, EntityNames.Player].map((x) => + [entity, otherEntity].find((y) => y.name === x), + ); + const [curry, player] = curryPlayerPair; + if (curry && player) { + this.handleCurryPlayerCollision(curry, player, game); + } + } + + private handleKeyDoorCollision(key: Entity, door: Entity, game: Game) { + game.removeEntity(key.id); + game.removeEntity(door.id); + } + + private handleCurryPlayerCollision( + curry: Entity, + _player: Entity, + game: Game, + ) { + game.removeEntity(curry.id); + game.stop(); + } +} diff --git a/src/engine/systems/Grid.ts b/src/engine/systems/Grid.ts index 8756320..915335b 100644 --- a/src/engine/systems/Grid.ts +++ b/src/engine/systems/Grid.ts @@ -1,4 +1,4 @@ -import { System, SystemNames } from "."; +import { Collision, System, SystemNames } from "."; import { Game } from ".."; import { Entity } from "../entities"; import { PhysicsConstants } from "../config"; @@ -13,8 +13,8 @@ import { Coord2D, Direction, Dimension2D } from "../interfaces"; import { clamp } from "../utils"; export class Grid extends System { - private dimension: Dimension2D; - private grid: Set[][] = []; + public dimension: Dimension2D; + public grid: Set[][] = []; constructor( { width: columns, height: rows }: Dimension2D, @@ -108,10 +108,10 @@ export class Grid extends System { grid.movingDirection = Direction.NONE; entity.addComponent(grid); // default to not moving - let nextGridPosition = this.getNewGridPosition( + let [currentPosition, nextGridPosition] = [ gridPosition, - movingDirection, - ); + this.getNewGridPosition(gridPosition, movingDirection), + ]; const moving = new Set(); moving.add(entity.id); @@ -122,11 +122,24 @@ export class Grid extends System { (id) => game.getEntity(id)!, ); - if ( - entities.some((entity) => - entity.hasComponent(ComponentNames.Colliding), + const collidingEntities = entities.filter((entity) => + entity.hasComponent(ComponentNames.Colliding), + ); + + if (collidingEntities.length > 0) { + // i.e. key going into a door or function going into an application + const allEntitiesInPreviousCellCanCollide = Array.from( + this.grid[currentPosition.y][currentPosition.x], ) - ) { + .map((id) => game.getEntity(id)!) + .every((entity) => + collidingEntities.every((collidingEntity) => + Collision.canCollide(entity.name, collidingEntity.name), + ), + ); + if (allEntitiesInPreviousCellCanCollide) { + break; + } moving.clear(); break; } @@ -148,6 +161,7 @@ export class Grid extends System { moving.add(pushableEntity.id); } + currentPosition = nextGridPosition; nextGridPosition = this.getNewGridPosition( nextGridPosition, movingDirection, @@ -324,7 +338,7 @@ export class Grid extends System { this.grid.forEach((row) => row.forEach((cell) => { for (const id of cell) { - if (movedEntities.has(id)) { + if (movedEntities.has(id) || !game.getEntity(id)) { cell.delete(id); } } diff --git a/src/engine/systems/SystemNames.ts b/src/engine/systems/SystemNames.ts index 85d1539..c96dc48 100644 --- a/src/engine/systems/SystemNames.ts +++ b/src/engine/systems/SystemNames.ts @@ -4,4 +4,5 @@ export namespace SystemNames { export const FacingDirection = "FacingDirection"; export const Grid = "Grid"; export const LambdaFactory = "LambdaFactory"; + export const Collision = "Collision"; } diff --git a/src/engine/systems/index.ts b/src/engine/systems/index.ts index 4490ee2..6ee5392 100644 --- a/src/engine/systems/index.ts +++ b/src/engine/systems/index.ts @@ -5,3 +5,4 @@ export * from "./Input"; export * from "./FacingDirection"; export * from "./Grid"; export * from "./LambdaFactory"; +export * from "./Collision";