/// export const game = true import type { GameContext, InputState } from "@/shared/game" import { randomElement, randomIndex } from "@/shared/utils.ts" const COLS = 10 const ROWS = 20 const CELL = 25 const DOWN_TICK = 10 const MOVE_TICK = 3 const TETRIS_FRAMES = 5 type Shape = { x: number, y: number, shape: string, rotation: number } let player: Shape let grid: string[][] = [] let dead = false let downTick = 0 let moveTick = 0 let tetrisRows: number[] = [] let tetrisTimer = 0 const COLORS: Record = { I: "cyan", O: "yellow", T: "purple", S: "green", Z: "red", J: "blue", L: "orange", } // I, O, T, L, J, S, Z const SHAPES: Record = { I: [ [[1, 1, 1, 1]], [[1], [1], [1], [1]] ], O: [[ [1, 1], [1, 1] ]], T: [ [ [0, 1, 0], [1, 1, 1], ], [ [1, 0], [1, 1], [1, 0] ], [ [1, 1, 1], [0, 1, 0], ], [ [0, 1], [1, 1], [0, 1] ] ], L: [ [ [1, 0], [1, 0], [1, 1] ], [ [1, 1, 1], [1, 0, 0] ], [ [1, 1], [0, 1], [0, 1] ], [ [0, 0, 1], [1, 1, 1] ] ], J: [ [ [0, 1], [0, 1], [1, 1] ], [ [1, 0, 0], [1, 1, 1] ], [ [1, 1], [1, 0], [1, 0] ], [ [1, 1, 1], [0, 0, 1] ] ], S: [ [ [0, 1, 1], [1, 1, 0] ], [ [1, 0], [1, 1], [0, 1] ], [ [0, 1, 1], [1, 1, 0], ], [ [1, 0], [1, 1], [0, 1] ] ], Z: [ [ [1, 1, 0], [0, 1, 1] ], [ [0, 1], [1, 1], [1, 0] ], [ [1, 1, 0], [0, 1, 1] ], [ [0, 1], [1, 1], [1, 0] ] ], } export function init() { dead = false downTick = 0 moveTick = 0 tetrisTimer = TETRIS_FRAMES tetrisRows.length = 0 grid.length = 0 grid[ROWS - 1] = [ "magenta", "magenta", "magenta", "magenta", "", "magenta", "magenta", "magenta", "magenta", "magenta", ] player = newShape() } export function update(_delta: number, input: InputState) { const keys = input.pressed if (input.justPressed.has(" ")) { player.rotation += 1 if (player.rotation === SHAPES[player.shape]!.length) player.rotation = 0 } // save to grid const shape = SHAPES[player.shape]![player.rotation]! if (shape) { let hit = false outer: for (const row in shape) { for (const col in shape[row]!) { const filled = shape[row][col] if (!filled) continue const x = player.x + Number(col) const y = player.y + Number(row) grid[y + 1] ??= [] if ((y + 1) >= ROWS || grid[y + 1]![x]) { hit = true break outer } } } if (tetrisRows.length > 0) { tetrisTimer-- if (tetrisTimer <= 0) { removeTetrisRows() tetrisRows = [] } } else if (anyFullRows()) { tetrisRows = findFullRows() tetrisTimer = TETRIS_FRAMES } if (hit) { const shape = SHAPES[player.shape]![player.rotation]! for (const row in shape) { for (const col in shape[row]!) { const filled = shape[row][col] if (!filled) continue const x = player.x + Number(col) const y = player.y + Number(row) grid[y] ??= [] grid[y][x] = COLORS[player.shape]! } } player = newShape() } } if (++moveTick % MOVE_TICK === 0) { if (keys.has("ArrowLeft") || keys.has("a")) player.x = Math.max(0, player.x - 1) if (keys.has("ArrowRight") || keys.has("d")) player.x = Math.min(COLS, player.x + 1) if (keys.has("ArrowDown") || keys.has("s")) { player.y += 1 } } if (++downTick % DOWN_TICK === 0) { player.y += 1 } } export function draw(game: GameContext) { // game.clear("#6C6FF6") game.clear("#654321") const boardW = COLS * CELL const boardH = ROWS * CELL const offsetX = (game.width - boardW) / 2 const offsetY = (game.height - boardH) / 2 const c = game.ctx c.save() c.translate(offsetX, offsetY) // background game.rectfill(0, 0, boardW, boardH, "black") // draw board for (let row = 0; row < ROWS; row++) { for (let col = 0; col < COLS; col++) { let color = grid[row]?.[col] || "gray" if (tetrisRows.includes(row)) color = "white" drawBlock(game, col, row, color) } } // player shape const shape = SHAPES[player.shape]![player.rotation]! for (const row in shape) { for (const col in shape[row]!) { const filled = shape[row][col] if (!filled) continue const x = player.x + Number(col) const y = player.y + Number(row) drawBlock(game, x, y, COLORS[player.shape]!) } } c.restore() // ya dead if (dead) { game.centerText("GAME OVER", "red", 24) } } function newShape(): Shape { const shape = randomElement(Object.keys(SHAPES))! return { x: 3, y: 0, shape, rotation: randomIndex(SHAPES[shape]!)! } } function anyFullRows(): boolean { return findFullRows().length > 0 } function findFullRows(): number[] { const rows: number[] = [] for (let y = 0; y < ROWS; y++) { let full = true for (let x = 0; x < COLS; x++) { if (!grid[y]?.[x]) { full = false break } } if (full) rows.push(y) } return rows } function removeTetrisRows() { const newGrid: string[][] = [] for (let y = 0; y < ROWS; y++) if (!tetrisRows.includes(y)) newGrid.push(grid[y] ?? []) // add empty rows on top to restore height while (newGrid.length < ROWS) newGrid.unshift([]) grid = newGrid tetrisRows = [] } function drawBlock(game: GameContext, x: number, y: number, color: string) { game.rectfill( x * CELL, y * CELL, ((x + 1) * CELL) - .5, ((y + 1) * CELL) - .5, color ) }