From 0ec9edd595024ac1693b2e43d8c694248d1b7840 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sun, 28 Sep 2025 18:26:56 -0700 Subject: [PATCH] snake! --- app/nose/bin/snake.ts | 106 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 app/nose/bin/snake.ts diff --git a/app/nose/bin/snake.ts b/app/nose/bin/snake.ts new file mode 100644 index 0000000..1089c96 --- /dev/null +++ b/app/nose/bin/snake.ts @@ -0,0 +1,106 @@ +/// +export const game = true + +import type { GameContext, InputState } from "@/shared/game" +import { rng } from "@/shared/utils.ts" + +const CELL = 20 +const WIDTH = 30 +const HEIGHT = 20 +const TICK = 5 + +let snake: { x: number; y: number }[] = [{ x: 5, y: 5 }] +let dir = { x: 1, y: 0 } +let food = { x: rng(WIDTH) - 1, y: rng(HEIGHT) - 1 } +let dead = false +let tick = 0 + +export function init() { + snake = [{ x: 5, y: 5 }] + dir = { x: 1, y: 0 } + food = { x: rng(WIDTH) - 1, y: rng(HEIGHT) - 1 } + dead = false + tick = 0 +} + +export function update(_delta: number, input: InputState) { + if (dead) return + + const keys = input.pressed + + if (keys.has("ArrowUp") || keys.has("w")) dir = { x: 0, y: -1 } + if (keys.has("ArrowDown") || keys.has("s")) dir = { x: 0, y: 1 } + if (keys.has("ArrowLeft") || keys.has("a")) dir = { x: -1, y: 0 } + if (keys.has("ArrowRight") || keys.has("d")) dir = { x: 1, y: 0 } + + // move every $TICK ticks + if (++tick % TICK !== 0) return + + const head = { x: snake[0]!.x + dir.x, y: snake[0]!.y + dir.y } + + // death checks + if ( + head.x < 0 || head.x >= WIDTH || + head.y < 0 || head.y >= HEIGHT || + snake.some(s => s.x === head.x && s.y === head.y) + ) { + dead = true + return + } + + snake.unshift(head) + + // eat + if (head.x === food.x && head.y === food.y) { + food = { x: rng(WIDTH) - 1, y: rng(HEIGHT) - 1 } + } else { + snake.pop() + } +} + +export function draw(ctx: GameContext) { + ctx.clear() + ctx.rectfill(0, 0, ctx.width, ctx.height, "black") + + const boardW = WIDTH * CELL + const boardH = HEIGHT * CELL + + // move board center → canvas center + const offsetX = (ctx.width - boardW) / 2 + const offsetY = (ctx.height - boardH) / 2 + + console.log("X", offsetX) + console.log("Y", offsetY) + + const c = ctx.ctx + c.save() + c.translate(offsetX, offsetY) + + // board background (now local 0,0 is board top-left) + ctx.rectfill(0, 0, boardW, boardH, "green") + + // food + ctx.rectfill( + food.x * CELL, food.y * CELL, + (food.x + 1) * CELL, (food.y + 1) * CELL, + "lime" + ) + + // snake + for (const s of snake) { + ctx.rectfill( + s.x * CELL, s.y * CELL, + (s.x + 1) * CELL, (s.y + 1) * CELL, + "magenta" + ) + } + + // score! + ctx.text(`Score: ${snake.length - 1}`, 5, boardH - 25, "cyan") + + c.restore() + + if (dead) { + ctx.centerText("GAME OVER", ctx.height / 2, "red", 50) + } +}