125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
import { defineComponent } from "../../src/component";
|
|
import type { World } from "../../src/index";
|
|
import type { CommandQueue } from "../../src/commands/index";
|
|
import {
|
|
Board,
|
|
Piece,
|
|
Score,
|
|
GameOver,
|
|
Paused,
|
|
TickTimer,
|
|
createPieceHelpers,
|
|
} from "./components";
|
|
import {
|
|
collides,
|
|
lockPiece,
|
|
clearLines,
|
|
scoreForLines,
|
|
tryRotate,
|
|
} from "./game";
|
|
|
|
// ── Command definitions ──────────────────────────────
|
|
|
|
/** Move the active piece left. */
|
|
export const MoveLeft = defineComponent("moveLeft", {});
|
|
|
|
/** Move the active piece right. */
|
|
export const MoveRight = defineComponent("moveRight", {});
|
|
|
|
/** Rotate the active piece clockwise. */
|
|
export const Rotate = defineComponent("rotate", {});
|
|
|
|
/** Soft drop — move piece down one row immediately. */
|
|
export const SoftDrop = defineComponent("softDrop", {});
|
|
|
|
/** Hard drop — slam piece to the bottom instantly. */
|
|
export const HardDrop = defineComponent("hardDrop", {});
|
|
|
|
/** Pause / unpause the game. */
|
|
export const TogglePause = defineComponent("togglePause", {});
|
|
|
|
/** Restart after game over. */
|
|
export const Restart = defineComponent("restart", {});
|
|
|
|
// ── Command handlers ─────────────────────────────────
|
|
|
|
export function registerCommands(
|
|
world: World,
|
|
commands: CommandQueue,
|
|
pieces: ReturnType<typeof createPieceHelpers>,
|
|
): void {
|
|
const hasActivePiece = () => world.hasSingleton(Piece);
|
|
const isBlocked = () =>
|
|
world.hasSingleton(GameOver) || world.hasSingleton(Paused);
|
|
|
|
commands.handle(MoveLeft, () => {
|
|
if (!hasActivePiece() || isBlocked()) return;
|
|
const piece = world.getSingleton(Piece);
|
|
const board = world.getSingleton(Board);
|
|
if (!collides(board.grid, piece.shape, piece.x - 1, piece.y)) {
|
|
piece.x--;
|
|
}
|
|
});
|
|
|
|
commands.handle(MoveRight, () => {
|
|
if (!hasActivePiece() || isBlocked()) return;
|
|
const piece = world.getSingleton(Piece);
|
|
const board = world.getSingleton(Board);
|
|
if (!collides(board.grid, piece.shape, piece.x + 1, piece.y)) {
|
|
piece.x++;
|
|
}
|
|
});
|
|
|
|
commands.handle(Rotate, () => {
|
|
if (!hasActivePiece() || isBlocked()) return;
|
|
const piece = world.getSingleton(Piece);
|
|
const board = world.getSingleton(Board);
|
|
const result = tryRotate(board.grid, piece.shape, piece.x, piece.y);
|
|
if (result) {
|
|
piece.shape = result.shape;
|
|
piece.x = result.x;
|
|
}
|
|
});
|
|
|
|
commands.handle(SoftDrop, () => {
|
|
if (!hasActivePiece() || isBlocked()) return;
|
|
const piece = world.getSingleton(Piece);
|
|
const board = world.getSingleton(Board);
|
|
if (!collides(board.grid, piece.shape, piece.x, piece.y + 1)) {
|
|
piece.y++;
|
|
}
|
|
});
|
|
|
|
commands.handle(HardDrop, () => {
|
|
if (!hasActivePiece() || isBlocked()) return;
|
|
const piece = world.getSingleton(Piece);
|
|
const board = world.getSingleton(Board);
|
|
while (!collides(board.grid, piece.shape, piece.x, piece.y + 1)) {
|
|
piece.y++;
|
|
}
|
|
pieces.lockAndSpawn();
|
|
});
|
|
|
|
commands.handle(TogglePause, () => {
|
|
if (world.hasSingleton(GameOver)) return;
|
|
if (world.hasSingleton(Paused)) {
|
|
world.removeSingleton(Paused);
|
|
} else {
|
|
world.addSingleton(Paused);
|
|
}
|
|
});
|
|
|
|
commands.handle(Restart, () => {
|
|
if (!world.hasSingleton(GameOver)) return;
|
|
const board = world.getSingleton(Board);
|
|
for (let r = 0; r < board.grid.length; r++) {
|
|
board.grid[r].fill(0);
|
|
}
|
|
world.setSingleton(Score, { points: 0, lines: 0, level: 1 });
|
|
world.setSingleton(TickTimer, { accumulator: 0, interval: 800 });
|
|
world.removeSingleton(GameOver);
|
|
if (world.hasSingleton(Piece)) world.removeSingleton(Piece);
|
|
pieces.spawnPiece();
|
|
});
|
|
}
|