From b5bfb7176102518deaae217939f3e321af0ea88c Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Fri, 25 Oct 2024 03:20:11 +0300 Subject: [PATCH] Visual feedback derived from the changes --- components/Visualizer.tsx | 4 +- renderer/index.ts | 72 +++++++++++++------ simulators/zombie-survival/Change.ts | 5 ++ simulators/zombie-survival/Position.ts | 4 ++ simulators/zombie-survival/ZombieSurvival.ts | 21 +++++- simulators/zombie-survival/entities/Entity.ts | 15 ++++ 6 files changed, 95 insertions(+), 26 deletions(-) create mode 100644 simulators/zombie-survival/Change.ts diff --git a/components/Visualizer.tsx b/components/Visualizer.tsx index 8af250a..daebf20 100644 --- a/components/Visualizer.tsx +++ b/components/Visualizer.tsx @@ -50,7 +50,7 @@ export function Visualizer({ function startSimulation() { simulator.current = new ZombieSurvival(map); - renderer.current?.render(simulator.current.getAllAliveEntities()); + renderer.current?.render(simulator.current); setRunning(true); interval.current = setInterval(() => { @@ -60,7 +60,7 @@ export function Visualizer({ if (!simulator.current.finished()) { simulator.current.step(); - renderer.current?.render(simulator.current.getAllAliveEntities()); + renderer.current?.render(simulator.current); return; } diff --git a/renderer/index.ts b/renderer/index.ts index db92c57..b25e4cc 100644 --- a/renderer/index.ts +++ b/renderer/index.ts @@ -1,4 +1,9 @@ -import { type Entity, EntityType } from "@/simulators/zombie-survival"; +import { + type Entity, + EntityType, + ZombieSurvival, +} from "@/simulators/zombie-survival"; +import { Change } from "@/simulators/zombie-survival/Change"; export interface RendererAssets { loading: boolean; @@ -8,7 +13,7 @@ export interface RendererAssets { player: HTMLImageElement | null; rock: HTMLImageElement | null; zombie: HTMLImageElement | null; - zombieHit: HTMLImageElement | null; + zombieWalking: HTMLImageElement | null; } const assets: RendererAssets = { @@ -19,7 +24,7 @@ const assets: RendererAssets = { player: null, rock: null, zombie: null, - zombieHit: null, + zombieWalking: null, }; async function loadAssets() { @@ -44,7 +49,7 @@ async function loadAssets() { assets.player = player; assets.rock = rock; assets.zombie = zombie; - assets.zombieHit = zombieHit; + assets.zombieWalking = zombieHit; } async function loadImage(src: string): Promise { @@ -67,8 +72,8 @@ function getEntityImage(entity: Entity): HTMLImageElement | null { return assets.rock; } case EntityType.Zombie: { - if (entity.getHealth() === 1) { - return assets.zombieHit; + if (entity.getChanges().includes(Change.Walking)) { + return assets.zombieWalking; } else { return assets.zombie; } @@ -82,7 +87,9 @@ export class Renderer { private readonly h: number; private readonly w: number; + private canvas2: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; + private ctx2: CanvasRenderingContext2D; public constructor( boardHeight: number, @@ -95,24 +102,37 @@ export class Renderer { this.h = boardHeight * cellSize; this.w = boardWidth * cellSize; + this.canvas2 = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + const ctx2 = this.canvas2.getContext("2d"); - if (ctx === null) { + if (ctx === null || ctx2 === null) { throw new Error("Unable to get 2d context"); } this.ctx = ctx; + this.ctx2 = ctx2; - canvas.setAttribute("height", `${this.h * window.devicePixelRatio}`); - canvas.setAttribute("width", `${this.w * window.devicePixelRatio}`); + canvas.height = this.h * window.devicePixelRatio; + canvas.width = this.w * window.devicePixelRatio; canvas.style.height = `${this.h}px`; canvas.style.width = `${this.w}px`; + this.canvas2.width = this.cellSize * window.devicePixelRatio; + this.canvas2.height = this.cellSize * window.devicePixelRatio; + this.canvas2.style.height = `${this.cellSize}px`; + this.canvas2.style.width = `${this.cellSize}px`; + ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + ctx2.scale(window.devicePixelRatio, window.devicePixelRatio); + void loadAssets(); } - public render(entities: Entity[]) { + public render(simulator: ZombieSurvival) { + const entities = simulator.getAllEntities(); + this.ctx.clearRect(0, 0, this.w, this.h); this.drawBg(); @@ -149,6 +169,10 @@ export class Renderer { } private drawEntity(entity: Entity) { + if (entity.dead()) { + return; + } + const entityImage = getEntityImage(entity); if (entityImage === null) { @@ -156,20 +180,24 @@ export class Renderer { } const entityPosition = entity.getPosition(); + const x = entityPosition.x * this.cellSize; + const y = entityPosition.y * this.cellSize; + + if (entity.getChanges().includes(Change.Hit)) { + this.ctx2.clearRect(0, 0, this.cellSize, this.cellSize); + + this.ctx2.filter = "hue-rotate(300deg)"; + this.ctx2.drawImage(entityImage, 0, 0, this.cellSize, this.cellSize); + this.ctx2.filter = "none"; - this.ctx.globalAlpha = - entity.getType() === EntityType.Zombie && entity.getHealth() === 1 - ? 0.5 - : 1; + this.ctx2.globalCompositeOperation = "destination-in"; + this.ctx2.fillRect(0, 0, this.cellSize, this.cellSize); + this.ctx2.globalCompositeOperation = "source-over"; - this.ctx.drawImage( - entityImage, - entityPosition.x * this.cellSize, - entityPosition.y * this.cellSize, - this.cellSize, - this.cellSize, - ); + this.ctx.drawImage(this.canvas2, x, y, this.cellSize, this.cellSize); + return; + } - this.ctx.globalAlpha = 1; + this.ctx.drawImage(entityImage, x, y, this.cellSize, this.cellSize); } } diff --git a/simulators/zombie-survival/Change.ts b/simulators/zombie-survival/Change.ts new file mode 100644 index 0000000..755c9d7 --- /dev/null +++ b/simulators/zombie-survival/Change.ts @@ -0,0 +1,5 @@ +export enum Change { + Hit, + Killed, + Walking, +} diff --git a/simulators/zombie-survival/Position.ts b/simulators/zombie-survival/Position.ts index 09614ba..74d7653 100644 --- a/simulators/zombie-survival/Position.ts +++ b/simulators/zombie-survival/Position.ts @@ -2,3 +2,7 @@ export interface Position { x: number; y: number; } + +export function samePosition(p1: Position, p2: Position): boolean { + return p1.x === p2.x && p1.y === p2.y; +} diff --git a/simulators/zombie-survival/ZombieSurvival.ts b/simulators/zombie-survival/ZombieSurvival.ts index 5bc40ac..263e5cd 100644 --- a/simulators/zombie-survival/ZombieSurvival.ts +++ b/simulators/zombie-survival/ZombieSurvival.ts @@ -1,4 +1,5 @@ -import { Position } from "./Position"; +import { Change } from "./Change"; +import { Position, samePosition } from "./Position"; import { Box } from "./entities/Box"; import { Entity } from "./entities/Entity"; import { Player } from "./entities/Player"; @@ -193,14 +194,30 @@ export class ZombieSurvival { } public step() { + const initialHealth = this.zombies.map((zombie) => zombie.getHealth()); + + this.player.clearChanges(); this.player.shoot(); - for (const zombie of this.zombies) { + for (let i = 0; i < this.zombies.length; i++) { + const zombie = this.zombies[i]; + if (this.player.dead()) { break; } + const initialPosition = zombie.getPosition(); + + zombie.clearChanges(); zombie.walk(); + + if (initialHealth[i] !== zombie.getHealth()) { + zombie.addChange(Change.Hit); + } + + if (!samePosition(initialPosition, zombie.getPosition())) { + zombie.addChange(Change.Walking); + } } } } diff --git a/simulators/zombie-survival/entities/Entity.ts b/simulators/zombie-survival/entities/Entity.ts index 92d911f..7ebee97 100644 --- a/simulators/zombie-survival/entities/Entity.ts +++ b/simulators/zombie-survival/entities/Entity.ts @@ -1,3 +1,4 @@ +import { type Change } from "../Change"; import { Position } from "../Position"; export enum EntityType { @@ -9,6 +10,7 @@ export enum EntityType { export class Entity { protected destructible: boolean; + protected changes: Change[]; protected health: number; protected position: Position; protected type: EntityType; @@ -20,15 +22,28 @@ export class Entity { position: Position, ) { this.destructible = destructible; + this.changes = []; this.health = health; this.position = position; this.type = type; } + public addChange(change: Change): void { + this.changes.push(change); + } + + public clearChanges(): void { + this.changes = []; + } + public dead(): boolean { return this.health === 0; } + public getChanges(): Change[] { + return this.changes; + } + public getPosition(): Position { return this.position; }