tetris!
This commit is contained in:
parent
ef3909ef3d
commit
3f6bacfcd3
278
app/nose/bin/tetris.ts
Normal file
278
app/nose/bin/tetris.ts
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
/// <reference lib="dom" />
|
||||
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
|
||||
|
||||
let player: { x: number, y: number, shape: string, rotation: number }
|
||||
let grid: string[][] = []
|
||||
let dead = false
|
||||
let downTick = 0
|
||||
let moveTick = 0
|
||||
|
||||
const COLORS: Record<string, string> = {
|
||||
I: "cyan",
|
||||
O: "yellow",
|
||||
T: "purple",
|
||||
S: "green",
|
||||
Z: "red",
|
||||
J: "blue",
|
||||
L: "orange",
|
||||
}
|
||||
|
||||
// I, O, T, L, J, S, Z
|
||||
const SHAPES: Record<string, number[][][]> = {
|
||||
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
|
||||
|
||||
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 (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++) {
|
||||
drawBlock(game, col, row, grid[row]?.[col] || "gray")
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
const shape = randomElement(Object.keys(SHAPES))!
|
||||
const newShape = { x: 3, y: 0, shape, rotation: randomIndex(SHAPES[shape]!)! }
|
||||
// const shape = SHAPES["O"]
|
||||
// const newShape = { x: 3, y: 0, shape, rotation: randomIndex(SHAPES[shape]!)! }
|
||||
console.log("new shape", newShape)
|
||||
return newShape
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user