wow, tetris

This commit is contained in:
Chris Wanstrath 2025-09-29 00:07:31 -07:00
parent bba8e32fb7
commit e33fb44c16

View File

@ -10,6 +10,7 @@ const CELL = 25
const DOWN_TICK = 10 const DOWN_TICK = 10
const MOVE_TICK = 3 const MOVE_TICK = 3
const TETRIS_FRAMES = 5 const TETRIS_FRAMES = 5
const LOCK_DELAY = 5
type Shape = { x: number, y: number, shape: string, rotation: number } type Shape = { x: number, y: number, shape: string, rotation: number }
@ -20,6 +21,7 @@ let downTick = 0
let moveTick = 0 let moveTick = 0
let tetrisRows: number[] = [] let tetrisRows: number[] = []
let tetrisTimer = 0 let tetrisTimer = 0
let lockTimer = 0
const COLORS: Record<string, string> = { const COLORS: Record<string, string> = {
I: "cyan", I: "cyan",
@ -154,22 +156,10 @@ export function init() {
downTick = 0 downTick = 0
moveTick = 0 moveTick = 0
tetrisTimer = TETRIS_FRAMES tetrisTimer = TETRIS_FRAMES
lockTimer = 0
tetrisRows.length = 0 tetrisRows.length = 0
grid.length = 0 grid.length = 0
grid[ROWS - 1] = [
"magenta",
"magenta",
"magenta",
"magenta",
"",
"magenta",
"magenta",
"magenta",
"magenta",
"magenta",
]
player = newShape() player = newShape()
} }
@ -182,66 +172,54 @@ export function update(_delta: number, input: InputState) {
player.rotation = 0 player.rotation = 0
} }
// save to grid // tetris animation
const shape = SHAPES[player.shape]![player.rotation]! if (tetrisRows.length > 0) {
if (shape) { tetrisTimer--
let hit = false if (tetrisTimer <= 0) {
outer: for (const row in shape) { removeTetrisRows()
for (const col in shape[row]!) { tetrisRows = []
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
}
}
} }
} else if (anyFullRows()) {
tetrisRows = findFullRows()
tetrisTimer = TETRIS_FRAMES
}
if (tetrisRows.length > 0) { const hit = blocked()
tetrisTimer-- if (!hit) lockTimer = LOCK_DELAY
if (tetrisTimer <= 0) {
removeTetrisRows()
tetrisRows = []
}
} else if (anyFullRows()) {
tetrisRows = findFullRows()
tetrisTimer = TETRIS_FRAMES
}
if (hit) { if (hit) {
const shape = SHAPES[player.shape]![player.rotation]! if (lockTimer > 0) {
for (const row in shape) { lockTimer--
for (const col in shape[row]!) { } else {
const filled = shape[row][col] lockShape()
if (!filled) continue return
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 (++moveTick % MOVE_TICK === 0) {
if (keys.has("ArrowLeft") || keys.has("a")) player.x = Math.max(0, player.x - 1) if (
if (keys.has("ArrowRight") || keys.has("d")) player.x = Math.min(COLS, player.x + 1) (keys.has("ArrowLeft") || keys.has("a")) &&
if (keys.has("ArrowDown") || keys.has("s")) { player.y += 1 } !collision(player.x - 1, player.y, player.rotation)
)
player.x = Math.max(0, player.x - 1)
if (
(keys.has("ArrowRight") || keys.has("d")) &&
!collision(player.x + 1, player.y, player.rotation)
)
player.x = Math.min(COLS, player.x + 1)
if (
(keys.has("ArrowDown") || keys.has("s")) &&
!collision(player.x, player.y + 1, player.rotation)
)
player.y += 1
} }
if (++downTick % DOWN_TICK === 0) { if (++downTick % DOWN_TICK === 0)
player.y += 1 if (!collision(player.x, player.y + 1, player.rotation)) {
} player.y++
}
} }
export function draw(game: GameContext) { export function draw(game: GameContext) {
@ -315,7 +293,6 @@ function findFullRows(): number[] {
return rows return rows
} }
function removeTetrisRows() { function removeTetrisRows() {
const newGrid: string[][] = [] const newGrid: string[][] = []
@ -331,6 +308,66 @@ function removeTetrisRows() {
tetrisRows = [] tetrisRows = []
} }
function collision(x: number, y: number, rotation: number): boolean {
const shape = SHAPES[player.shape]![rotation]!
for (let row = 0; row < shape.length; row++) {
for (let col = 0; col < shape[row]!.length; col++) {
if (!shape[row]![col]) continue
const nx = x + col
const ny = y + row
if (nx < 0 || nx >= COLS) return true
if (ny >= ROWS) return true
if (grid[ny]?.[nx]) return true
}
}
return false
}
function lockShape() {
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()
}
function blocked(): boolean {
const shape = SHAPES[player.shape]![player.rotation]!
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
}
}
}
return hit
}
function drawBlock(game: GameContext, x: number, y: number, color: string) { function drawBlock(game: GameContext, x: number, y: number, color: string) {
game.rectfill( game.rectfill(