Skip to content

Commit

Permalink
Visual feedback derived from the changes
Browse files Browse the repository at this point in the history
  • Loading branch information
delasy committed Oct 25, 2024
1 parent 0ae0fe6 commit b5bfb71
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 26 deletions.
4 changes: 2 additions & 2 deletions components/Visualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -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;
}

Expand Down
72 changes: 50 additions & 22 deletions renderer/index.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 = {
Expand All @@ -19,7 +24,7 @@ const assets: RendererAssets = {
player: null,
rock: null,
zombie: null,
zombieHit: null,
zombieWalking: null,
};

async function loadAssets() {
Expand All @@ -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<HTMLImageElement> {
Expand All @@ -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;
}
Expand All @@ -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,
Expand All @@ -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();

Expand Down Expand Up @@ -149,27 +169,35 @@ export class Renderer {
}

private drawEntity(entity: Entity) {
if (entity.dead()) {
return;
}

const entityImage = getEntityImage(entity);

if (entityImage === null) {
return;
}

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);
}
}
5 changes: 5 additions & 0 deletions simulators/zombie-survival/Change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum Change {
Hit,
Killed,
Walking,
}
4 changes: 4 additions & 0 deletions simulators/zombie-survival/Position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
21 changes: 19 additions & 2 deletions simulators/zombie-survival/ZombieSurvival.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
}
}
}
}
15 changes: 15 additions & 0 deletions simulators/zombie-survival/entities/Entity.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type Change } from "../Change";
import { Position } from "../Position";

export enum EntityType {
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down

0 comments on commit b5bfb71

Please sign in to comment.