1
0
Fork 0

Fix random, fix dead picking

This commit is contained in:
Pabloader 2025-06-27 08:47:54 +00:00
parent bdca1ce602
commit c793d0a20c
5 changed files with 81 additions and 58 deletions

View File

@ -8,21 +8,22 @@ import TileMap from "./tilemap";
import Inventory from "./inventory"; import Inventory from "./inventory";
const MAP_SIZE = 12; const MAP_SIZE = 12;
const MAP_PIXEL_SIZE = 1000; const MAP_PIXEL_SIZE = window.innerHeight;
const MAP_PADDING = 10; const MAP_PADDING = 10;
const TILE_SIZE = (MAP_PIXEL_SIZE - MAP_PADDING * 2) / MAP_SIZE; const TILE_SIZE = (MAP_PIXEL_SIZE - MAP_PADDING * 2) / MAP_SIZE;
const SPINNER_SIZE = 200; const SIDEBAR_SIZE = Math.min((window.innerWidth - window.innerHeight) / 2, 400);
const canvas = createCanvas(MAP_PIXEL_SIZE + SPINNER_SIZE, MAP_PIXEL_SIZE);
const spinner = new Spinner([MAP_PIXEL_SIZE, 0], [SPINNER_SIZE, SPINNER_SIZE]); const canvas = createCanvas(MAP_PIXEL_SIZE + SIDEBAR_SIZE * 2, MAP_PIXEL_SIZE);
const map = new TileMap( const map = new TileMap(
[MAP_PADDING, MAP_PADDING], [MAP_PADDING, MAP_PADDING],
MAP_SIZE, MAP_SIZE,
TILE_SIZE, TILE_SIZE,
); );
const spinner = new Spinner([MAP_PIXEL_SIZE, 0], [SIDEBAR_SIZE, SIDEBAR_SIZE]);
const inventory = new Inventory( const inventory = new Inventory(
map.characters, map,
[MAP_PIXEL_SIZE, SPINNER_SIZE], [MAP_PIXEL_SIZE + SIDEBAR_SIZE, 0],
[SPINNER_SIZE, MAP_PIXEL_SIZE - SPINNER_SIZE], [SIDEBAR_SIZE, MAP_PIXEL_SIZE],
); );
const entities: Entity[] = [ const entities: Entity[] = [
@ -65,7 +66,6 @@ export default async function main() {
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
spinner.addListener((a) => map.handleSpin(a)); spinner.addListener((a) => map.handleSpin(a));
inventory.addListener((c, i) => map.handleItemUse(c, i));
canvas.addEventListener('click', onClick); canvas.addEventListener('click', onClick);
canvas.addEventListener('mousemove', onMouseMove); canvas.addEventListener('mousemove', onMouseMove);

View File

@ -4,14 +4,14 @@ import { Characters } from "./character";
import Entity from "./entity"; import Entity from "./entity";
import Tile from "./tile"; import Tile from "./tile";
import type Item from "./item"; import type Item from "./item";
import type TileMap from "./tilemap";
export type UseListener = (character: Character, item: Item) => void; export type UseListener = (character: Character, item: Item) => void;
export default class Inventory extends Entity { export default class Inventory extends Entity {
private tiles: Tile[][]; private tiles: Tile[][];
private listeners = new Set<UseListener>();
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); super(position, size);
const numCharacters = Object.keys(Characters).length; 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) { private drawCharacter(ctx: CanvasRenderingContext2D, idx: number) {
const character = this.characters[idx]; const character = this.characters[idx];
ctx.drawImage(character.type, 0.1, 0.1, 0.8, 0.8); ctx.drawImage(character.type, 0.1, 0.1, 0.8, 0.8);
ctx.fillText(`💖 ${character.health}`, 0.5, 1.5); 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; let y = 2;
for (const item of character.inventory) { for (const item of character.inventory) {
@ -40,7 +50,7 @@ export default class Inventory extends Entity {
public override handleClick(x: number, y: number): void { public override handleClick(x: number, y: number): void {
for (const { tile, item, character } of this.activeTiles) { for (const { tile, item, character } of this.activeTiles) {
if (tile.isPointInBounds(x - this.left, y - this.top)) { if (tile.isPointInBounds(x - this.left, y - this.top)) {
this.listeners.forEach(l => l(character, item)); this.map.handleItemUse(character, item);
break; 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 { protected override draw(ctx: CanvasRenderingContext2D): void {
const step = 1 / Object.keys(Characters).length; const step = 1 / Object.keys(Characters).length;
const columnWidth = this.width * step; const columnWidth = this.width * step;

View File

@ -1,3 +1,4 @@
import { randInt } from "@common/utils";
import Entity from "./entity"; import Entity from "./entity";
export enum SpinnerAction { export enum SpinnerAction {
@ -15,9 +16,8 @@ export default class Spinner extends Entity {
private readonly symbols = ['🏃‍♂️', '🧟‍♂️', '🔪', '🎯']; private readonly symbols = ['🏃‍♂️', '🧟‍♂️', '🔪', '🎯'];
private readonly startAngle = -Math.PI / 2 - this.probabilities[0] * 2 * Math.PI; private readonly startAngle = -Math.PI / 2 - this.probabilities[0] * 2 * Math.PI;
private angle = this.startAngle; private angle = -Math.PI / 2;
private speed = 0; private nextAngle = this.angle;
private friction = 0.3;
private fired = true; private fired = true;
private listeners = new Set<SpinnerListener>(); private listeners = new Set<SpinnerListener>();
@ -83,22 +83,19 @@ export default class Spinner extends Entity {
public override update(dt: number) { public override update(dt: number) {
if (this.fired) return; if (this.fired) return;
if (this.speed < 0.1) { if (this.nextAngle - this.angle <= 0.1) {
this.fire(); this.fire();
return; return;
} }
this.angle += this.speed * dt; this.angle += (this.nextAngle - this.angle) * dt;
this.speed *= 1.0 - this.friction * dt;
this.friction += 0.7 * dt;
} }
public override onClick() { public override onClick() {
if (!this.fired) return; if (!this.fired) return;
this.fired = false; this.fired = false;
this.speed = 25 + Math.random() * 25; this.nextAngle = this.angle + (randInt(5, 10) + Math.random()) * 2 * Math.PI;
this.friction = 0.3 + Math.random() * 0.3;
} }
public addListener(listener: SpinnerListener) { public addListener(listener: SpinnerListener) {
@ -128,6 +125,6 @@ export default class Spinner extends Entity {
angle = nextAngle; angle = nextAngle;
} }
checkAngle += Math.PI * 2; checkAngle += Math.PI * 2;
} }
} }
} }

View File

@ -4,6 +4,9 @@ import type Item from "./item";
export enum TileType { export enum TileType {
NORMAL, NORMAL,
START, START,
DOOR,
LOCKED_DOOR,
DOORWAY,
END, END,
} }
@ -40,6 +43,13 @@ export default class Tile extends Entity {
if (this.isOpen) { if (this.isOpen) {
const item = this.items[0]; const item = this.items[0];
ctx.drawImage(item.type, 0.1, 0.1, 0.8, 0.8); 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 { } else {
ctx.fillStyle = 'white'; ctx.fillStyle = 'white';
ctx.fillRect(0.1, 0.1, 0.8, 0.8); 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); return this.items.find(i => i.isEnemy);
} }
public open(): Item[] { public open() {
if (!this.isOpen) { this.isOpen = true;
this.isOpen = true;
const { pickable = [], notPickable = [] } = Object.groupBy(
this.items,
(i) => i.isPickable ? 'pickable' : 'notPickable',
);
this.items = notPickable;
return pickable;
}
return [];
} }
public removeItem(item: Item | null | undefined): boolean { public removeItem(item: Item | null | undefined): boolean {

View File

@ -103,6 +103,27 @@ export default class TileMap extends Entity {
map[x][y].type = TileType.END; 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.tiles = map.flat();
this.startTile = startTile; this.startTile = startTile;
@ -150,7 +171,8 @@ export default class TileMap extends Entity {
const endTiles = this.tiles.filter(t => t.type === TileType.END); const endTiles = this.tiles.filter(t => t.type === TileType.END);
const endTilesNeighbors = new Set(endTiles.flatMap(t => t.connections)); 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 t.type === TileType.NORMAL
&& !endTilesNeighbors.has(t) && !endTilesNeighbors.has(t)
&& !this.startTile.connections.includes(t) && !this.startTile.connections.includes(t)
@ -159,6 +181,7 @@ export default class TileMap extends Entity {
const fillableTiles = [ const fillableTiles = [
...endTilesNeighbors, ...endTilesNeighbors,
...this.startTile.connections, ...this.startTile.connections,
...doorTiles,
...shuffle(normalTiles), ...shuffle(normalTiles),
]; ];
@ -190,24 +213,26 @@ export default class TileMap extends Entity {
const path = Pathfinding.findPath(this.character.tile, tile); const path = Pathfinding.findPath(this.character.tile, tile);
if (path.length > 1) { if (path.length > 1) {
this.character.tile = tile; this.character.tile = tile;
const items = tile.open(); tile.open();
this.character.inventory.push(...items); for (const item of tile.items.slice()) { // iterate remaining items
if (tile.items.length > 0) { if (item.isPickable) {
for (const item of tile.items) { // iterate remaining items if (!tile.enemy) {
if (item instanceof Character) {
tile.removeItem(item); tile.removeItem(item);
this.characters.push(item); this.character.inventory.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}`);
} }
} 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(); this.nextCharacter();
} }
} }