2026-06-01 23:18:07 +08:00
|
|
|
|
import { defineComponent } from "../../src/component";
|
2026-06-02 10:39:27 +08:00
|
|
|
|
import type { World } from "../../src/index";
|
|
|
|
|
|
import {
|
|
|
|
|
|
randomPiece,
|
|
|
|
|
|
collides,
|
|
|
|
|
|
lockPiece,
|
|
|
|
|
|
clearLines,
|
|
|
|
|
|
scoreForLines,
|
|
|
|
|
|
BOARD_W,
|
|
|
|
|
|
} from "./game";
|
|
|
|
|
|
|
|
|
|
|
|
// ── Component definitions ────────────────────────────
|
2026-06-01 23:18:07 +08:00
|
|
|
|
|
|
|
|
|
|
/** 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
|
|
|
|
|
|
});
|
2026-06-02 10:39:27 +08:00
|
|
|
|
|
|
|
|
|
|
// ── 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|