ecs-observable/examples/tetris/components.ts

84 lines
2.6 KiB
TypeScript
Raw Normal View History

import { defineComponent } from "../../src/component";
import type { World } from "../../src/index";
import {
randomPiece,
collides,
lockPiece,
clearLines,
scoreForLines,
BOARD_W,
} from "./game";
// ── Component definitions ────────────────────────────
/** The playfield grid (20 rows × 10 cols). 0 = empty, non-zero = color index. */
export const Board = defineComponent("board", {
grid: Array.from({ length: 20 }, () => new Uint8Array(10)) as Uint8Array[],
});
// ── Active piece ─────────────────────────────────────
export const Piece = defineComponent("piece", {
shape: [] as number[][],
color: 1,
x: 3,
y: 0,
});
// ── Score / state ────────────────────────────────────
export const Score = defineComponent("score", {
points: 0,
lines: 0,
level: 1,
});
export const GameOver = defineComponent("gameOver", {});
export const Paused = defineComponent("paused", {});
// ── Timing ───────────────────────────────────────────
export const TickTimer = defineComponent("tickTimer", {
accumulator: 0,
interval: 800, // ms between gravity ticks
});
// ── Piece helpers ────────────────────────────────────
export function createPieceHelpers(world: World) {
return {
spawnPiece(): void {
const p = randomPiece();
world.addSingleton(Piece, {
shape: p.shape,
color: p.color,
x: Math.floor((BOARD_W - p.shape[0].length) / 2),
y: 0,
});
},
lockAndSpawn(): void {
const piece = world.getSingleton(Piece);
const board = world.getSingleton(Board);
lockPiece(board.grid, piece.shape, piece.color, piece.x, piece.y);
world.removeSingleton(Piece);
const cleared = clearLines(board.grid);
if (cleared > 0) {
const score = world.getSingleton(Score);
score.lines += cleared;
score.points += scoreForLines(cleared, score.level);
score.level = Math.floor(score.lines / 10) + 1;
const timer = world.getSingleton(TickTimer);
timer.interval = Math.max(100, 800 - (score.level - 1) * 70);
}
this.spawnPiece();
const newPiece = world.getSingleton(Piece);
if (collides(board.grid, newPiece.shape, newPiece.x, newPiece.y)) {
world.removeSingleton(Piece);
world.addSingleton(GameOver);
}
},
};
}