From c793d0a20c368d6775288cb9e7a5221e19420558 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Fri, 27 Jun 2025 08:47:54 +0000 Subject: [PATCH] Fix random, fix dead picking --- src/games/zombies/index.ts | 16 +++++----- src/games/zombies/inventory.ts | 24 +++++++------- src/games/zombies/spinner.ts | 17 +++++----- src/games/zombies/tile.ts | 25 +++++++-------- src/games/zombies/tilemap.ts | 57 ++++++++++++++++++++++++---------- 5 files changed, 81 insertions(+), 58 deletions(-) diff --git a/src/games/zombies/index.ts b/src/games/zombies/index.ts index ce8ebaa..86f8483 100644 --- a/src/games/zombies/index.ts +++ b/src/games/zombies/index.ts @@ -8,21 +8,22 @@ import TileMap from "./tilemap"; import Inventory from "./inventory"; const MAP_SIZE = 12; -const MAP_PIXEL_SIZE = 1000; +const MAP_PIXEL_SIZE = window.innerHeight; const MAP_PADDING = 10; const TILE_SIZE = (MAP_PIXEL_SIZE - MAP_PADDING * 2) / MAP_SIZE; -const SPINNER_SIZE = 200; -const canvas = createCanvas(MAP_PIXEL_SIZE + SPINNER_SIZE, MAP_PIXEL_SIZE); -const spinner = new Spinner([MAP_PIXEL_SIZE, 0], [SPINNER_SIZE, SPINNER_SIZE]); +const SIDEBAR_SIZE = Math.min((window.innerWidth - window.innerHeight) / 2, 400); + +const canvas = createCanvas(MAP_PIXEL_SIZE + SIDEBAR_SIZE * 2, MAP_PIXEL_SIZE); const map = new TileMap( [MAP_PADDING, MAP_PADDING], MAP_SIZE, TILE_SIZE, ); +const spinner = new Spinner([MAP_PIXEL_SIZE, 0], [SIDEBAR_SIZE, SIDEBAR_SIZE]); const inventory = new Inventory( - map.characters, - [MAP_PIXEL_SIZE, SPINNER_SIZE], - [SPINNER_SIZE, MAP_PIXEL_SIZE - SPINNER_SIZE], + map, + [MAP_PIXEL_SIZE + SIDEBAR_SIZE, 0], + [SIDEBAR_SIZE, MAP_PIXEL_SIZE], ); const entities: Entity[] = [ @@ -65,7 +66,6 @@ export default async function main() { const ctx = canvas.getContext('2d'); spinner.addListener((a) => map.handleSpin(a)); - inventory.addListener((c, i) => map.handleItemUse(c, i)); canvas.addEventListener('click', onClick); canvas.addEventListener('mousemove', onMouseMove); diff --git a/src/games/zombies/inventory.ts b/src/games/zombies/inventory.ts index 3b821ea..bf81daf 100644 --- a/src/games/zombies/inventory.ts +++ b/src/games/zombies/inventory.ts @@ -4,14 +4,14 @@ import { Characters } from "./character"; import Entity from "./entity"; import Tile from "./tile"; import type Item from "./item"; +import type TileMap from "./tilemap"; export type UseListener = (character: Character, item: Item) => void; export default class Inventory extends Entity { private tiles: Tile[][]; - private listeners = new Set(); - constructor(public readonly characters: Character[], position: [number, number], size: [number, number]) { + constructor(public readonly map: TileMap, position: [number, number], size: [number, number]) { super(position, size); const numCharacters = Object.keys(Characters).length; @@ -25,10 +25,20 @@ export default class Inventory extends Entity { ); } + private get characters() { + return this.map.characters; + } + private drawCharacter(ctx: CanvasRenderingContext2D, idx: number) { const character = this.characters[idx]; ctx.drawImage(character.type, 0.1, 0.1, 0.8, 0.8); ctx.fillText(`💖 ${character.health}`, 0.5, 1.5); + if (character === this.map.character) { + ctx.strokeStyle = 'black'; + ctx.lineWidth = 0.03; + + ctx.strokeRect(0.1, 0.1, 0.8, 0.8); + } let y = 2; for (const item of character.inventory) { @@ -40,7 +50,7 @@ export default class Inventory extends Entity { public override handleClick(x: number, y: number): void { for (const { tile, item, character } of this.activeTiles) { if (tile.isPointInBounds(x - this.left, y - this.top)) { - this.listeners.forEach(l => l(character, item)); + this.map.handleItemUse(character, item); break; } } @@ -63,14 +73,6 @@ export default class Inventory extends Entity { ); } - public addListener(listener: UseListener) { - this.listeners.add(listener); - } - - public removeListener(listener: UseListener) { - this.listeners.delete(listener); - } - protected override draw(ctx: CanvasRenderingContext2D): void { const step = 1 / Object.keys(Characters).length; const columnWidth = this.width * step; diff --git a/src/games/zombies/spinner.ts b/src/games/zombies/spinner.ts index 5ec658c..d798af6 100644 --- a/src/games/zombies/spinner.ts +++ b/src/games/zombies/spinner.ts @@ -1,3 +1,4 @@ +import { randInt } from "@common/utils"; import Entity from "./entity"; export enum SpinnerAction { @@ -15,9 +16,8 @@ export default class Spinner extends Entity { private readonly symbols = ['🏃‍♂️', '🧟‍♂️', '🔪', '🎯']; private readonly startAngle = -Math.PI / 2 - this.probabilities[0] * 2 * Math.PI; - private angle = this.startAngle; - private speed = 0; - private friction = 0.3; + private angle = -Math.PI / 2; + private nextAngle = this.angle; private fired = true; private listeners = new Set(); @@ -83,22 +83,19 @@ export default class Spinner extends Entity { public override update(dt: number) { if (this.fired) return; - if (this.speed < 0.1) { + if (this.nextAngle - this.angle <= 0.1) { this.fire(); return; } - this.angle += this.speed * dt; - this.speed *= 1.0 - this.friction * dt; - this.friction += 0.7 * dt; + this.angle += (this.nextAngle - this.angle) * dt; } public override onClick() { if (!this.fired) return; this.fired = false; - this.speed = 25 + Math.random() * 25; - this.friction = 0.3 + Math.random() * 0.3; + this.nextAngle = this.angle + (randInt(5, 10) + Math.random()) * 2 * Math.PI; } public addListener(listener: SpinnerListener) { @@ -128,6 +125,6 @@ export default class Spinner extends Entity { angle = nextAngle; } checkAngle += Math.PI * 2; - } + } } } diff --git a/src/games/zombies/tile.ts b/src/games/zombies/tile.ts index 450aa9f..728e5bb 100644 --- a/src/games/zombies/tile.ts +++ b/src/games/zombies/tile.ts @@ -4,6 +4,9 @@ import type Item from "./item"; export enum TileType { NORMAL, START, + DOOR, + LOCKED_DOOR, + DOORWAY, END, } @@ -40,6 +43,13 @@ export default class Tile extends Entity { if (this.isOpen) { const item = this.items[0]; ctx.drawImage(item.type, 0.1, 0.1, 0.8, 0.8); + + if (this.items.length > 1) { + ctx.strokeStyle = 'red'; + ctx.lineWidth = 2 / this.width; + + ctx.strokeRect(0.1, 0.1, 0.8, 0.8); + } } else { ctx.fillStyle = 'white'; ctx.fillRect(0.1, 0.1, 0.8, 0.8); @@ -55,19 +65,8 @@ export default class Tile extends Entity { return this.items.find(i => i.isEnemy); } - public open(): Item[] { - if (!this.isOpen) { - this.isOpen = true; - const { pickable = [], notPickable = [] } = Object.groupBy( - this.items, - (i) => i.isPickable ? 'pickable' : 'notPickable', - ); - - this.items = notPickable; - return pickable; - } - - return []; + public open() { + this.isOpen = true; } public removeItem(item: Item | null | undefined): boolean { diff --git a/src/games/zombies/tilemap.ts b/src/games/zombies/tilemap.ts index 70c7f25..3191768 100644 --- a/src/games/zombies/tilemap.ts +++ b/src/games/zombies/tilemap.ts @@ -103,6 +103,27 @@ export default class TileMap extends Entity { map[x][y].type = TileType.END; } + const doors = [ + [2, 4, 1, 4], + [2, 7, 1, 7], + [3, 5, 4, 5], + [4, 6, 4, 5], + [4, 10, 4, 9], + [5, 3, 5, 2], + [5, 10, 5, 9], + [6, 4, 7, 4], + [6, 7, 7, 7], + [8, 10, 8, 9], + [9, 6, 9, 5], + [10, 4, 11, 4], + [10, 7, 11, 7], + ]; + + for (const [doorX, doorY, wayX, wayY] of doors) { + map[doorX][doorY].type = TileType.DOOR; + map[wayX][wayY].type = TileType.DOORWAY; + } + this.tiles = map.flat(); this.startTile = startTile; @@ -150,7 +171,8 @@ export default class TileMap extends Entity { const endTiles = this.tiles.filter(t => t.type === TileType.END); const endTilesNeighbors = new Set(endTiles.flatMap(t => t.connections)); - const normalTiles = this.tiles.filter(t => + const doorTiles = this.tiles.filter(t => t.type === TileType.DOOR || t.type === TileType.DOORWAY); + const normalTiles = this.tiles.filter(t => t.type === TileType.NORMAL && !endTilesNeighbors.has(t) && !this.startTile.connections.includes(t) @@ -159,6 +181,7 @@ export default class TileMap extends Entity { const fillableTiles = [ ...endTilesNeighbors, ...this.startTile.connections, + ...doorTiles, ...shuffle(normalTiles), ]; @@ -190,24 +213,26 @@ export default class TileMap extends Entity { const path = Pathfinding.findPath(this.character.tile, tile); if (path.length > 1) { this.character.tile = tile; - const items = tile.open(); - this.character.inventory.push(...items); - if (tile.items.length > 0) { - for (const item of tile.items) { // iterate remaining items - if (item instanceof Character) { + tile.open(); + for (const item of tile.items.slice()) { // iterate remaining items + if (item.isPickable) { + if (!tile.enemy) { tile.removeItem(item); - this.characters.push(item); - this.nextCharacter(); - } else if (item.isBoss && this.character.removeItem(this.character.rocketLauncher)) { - tile.killEnemy(); - this.nextCharacter(); - } else if (item.isEnemy) { - this.state = GameState.FIGHT; - } else { - alert(`Unknown item found: ${item}`); + this.character.inventory.push(item); } + } else if (item instanceof Character) { + tile.removeItem(item); + this.characters.push(item); + } else if (item.isBoss && this.character.removeItem(this.character.rocketLauncher)) { + tile.killEnemy(); + } else if (item.isEnemy) { + this.state = GameState.FIGHT; + break; + } else { + alert(`Unknown item found: ${item}`); } - } else { + } + if (this.state === GameState.NORMAL) { this.nextCharacter(); } }