From 419a8328fc846ff65b5802c0f58f29aca71fbf9a Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sat, 27 Sep 2025 20:12:06 -0700 Subject: [PATCH] it's alive --- app/nose/bin/game.tsx | 3 +- app/src/js/game.ts | 9 +-- app/src/shared/game.ts | 143 ++++++++++++++++++++++++++++++++++++++++ app/src/shared/types.ts | 143 ---------------------------------------- 4 files changed, 150 insertions(+), 148 deletions(-) create mode 100644 app/src/shared/game.ts diff --git a/app/nose/bin/game.tsx b/app/nose/bin/game.tsx index 9ba74c2..de247af 100644 --- a/app/nose/bin/game.tsx +++ b/app/nose/bin/game.tsx @@ -1,7 +1,8 @@ /// export const game = true -import type { GameContext, InputState } from "@/shared/types" +import type { InputState } from "@/shared/types" +import type { GameContext } from "@/shared/game" import { rng } from "@/shared/utils.ts" const WIDTH = 960 diff --git a/app/src/js/game.ts b/app/src/js/game.ts index 5f323d7..45e263b 100644 --- a/app/src/js/game.ts +++ b/app/src/js/game.ts @@ -1,5 +1,5 @@ import type { Message, InputState } from "../shared/types.js" -import { GameContext } from "../shared/types.js" +import { GameContext } from "../shared/game.js" import { focusInput } from "./focus.js" import { $ } from "./dom.js" import { randomId } from "../shared/utils.js" @@ -8,7 +8,7 @@ import { browserCommands } from "./commands.js" const FPS = 30 let oldMode = "cinema" -let stopGame = false +let running = false let canvas: HTMLCanvasElement type Game = { init?: () => void, update?: (delta: number, input: InputState) => void, draw?: (ctx: GameContext) => void } @@ -56,7 +56,7 @@ function handleKeydown(e: KeyboardEvent) { e.preventDefault() if (e.key === "Escape" || (e.ctrlKey && e.key === "c")) { - stopGame = true + running = false if (oldMode === "tall") browserCommands.mode?.() canvas.classList.remove("active") canvas.style.height = canvas.height / 2 + "px" @@ -81,12 +81,13 @@ function handleKeyup(e: KeyboardEvent) { } function gameLoop(ctx: GameContext, game: Game) { + running = true let last = 0 if (game.init) game.init() function loop(ts: number) { - if (stopGame) return + if (!running) return const delta = ts - last if (delta >= 1000 / FPS) { diff --git a/app/src/shared/game.ts b/app/src/shared/game.ts new file mode 100644 index 0000000..aae325d --- /dev/null +++ b/app/src/shared/game.ts @@ -0,0 +1,143 @@ + +export class GameContext { + constructor(public ctx: CanvasRenderingContext2D) { } + + get width() { return this.ctx.canvas.width } + get height() { return this.ctx.canvas.height } + + clear() { + this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height) + } + + circ(x: number, y: number, r: number, color = "black") { + const c = this.ctx + c.save() + c.beginPath() + c.strokeStyle = color + c.arc(x, y, r, 0, Math.PI * 2) + c.stroke() + c.restore() + } + + circfill(x: number, y: number, r: number, color = "black") { + const c = this.ctx + c.save() + c.beginPath() + c.fillStyle = color + c.arc(x, y, r, 0, Math.PI * 2) + c.fill() + c.restore() + } + + line(x0: number, y0: number, x1: number, y1: number, color = "black") { + const c = this.ctx + c.save() + c.beginPath() + c.strokeStyle = color + c.moveTo(x0, y0) + c.lineTo(x1, y1) + c.stroke() + c.restore() + } + + oval(x0: number, y0: number, x1: number, y1: number, color = "black") { + const c = this.ctx + const w = x1 - x0 + const h = y1 - y0 + c.save() + c.beginPath() + c.strokeStyle = color + c.ellipse(x0 + w / 2, y0 + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2) + c.stroke() + c.restore() + } + + ovalfill(x0: number, y0: number, x1: number, y1: number, color = "black") { + const c = this.ctx + const w = x1 - x0 + const h = y1 - y0 + c.save() + c.beginPath() + c.fillStyle = color + c.ellipse(x0 + w / 2, y0 + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2) + c.fill() + c.restore() + } + + rect(x0: number, y0: number, x1: number, y1: number, color = "black") { + const c = this.ctx + c.save() + c.beginPath() + c.strokeStyle = color + c.rect(x0, y0, x1 - x0, y1 - y0) + c.stroke() + c.restore() + } + + rectfill(x0: number, y0: number, x1: number, y1: number, color = "black") { + const c = this.ctx + c.save() + this.ctx.fillStyle = color + this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0) + c.restore() + } + + rrect(x: number, y: number, w: number, h: number, r: number, color = "black") { + const c = this.ctx + c.save() + c.beginPath() + c.strokeStyle = color + this.roundRectPath(x, y, w, h, r) + c.stroke() + c.restore() + } + + rrectfill(x: number, y: number, w: number, h: number, r: number, color = "black") { + const c = this.ctx + c.save() + c.beginPath() + c.fillStyle = color + this.roundRectPath(x, y, w, h, r) + c.fill() + c.restore() + } + + trianglefill(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, color = "black") { + return this.polygonfill( + [ + [x0, y0], + [x1, y1], + [x2, y2] + ], + color + ) + } + + polygonfill(points: [number, number][], color = "black") { + if (points.length < 3) return // need at least a triangle + const c = this.ctx + c.save() + c.beginPath() + c.fillStyle = color + c.moveTo(points[0]![0], points[0]![1]) + for (let i = 1; i < points.length; i++) { + c.lineTo(points[i]![0], points[i]![1]) + } + c.closePath() + c.fill() + c.restore() + } + + private roundRectPath(x: number, y: number, w: number, h: number, r: number) { + const c = this.ctx + c.moveTo(x + r, y) + c.lineTo(x + w - r, y) + c.quadraticCurveTo(x + w, y, x + w, y + r) + c.lineTo(x + w, y + h - r) + c.quadraticCurveTo(x + w, y + h, x + w - r, y + h) + c.lineTo(x + r, y + h) + c.quadraticCurveTo(x, y + h, x, y + h - r) + c.lineTo(x, y + r) + c.quadraticCurveTo(x, y, x + r, y) + } +} \ No newline at end of file diff --git a/app/src/shared/types.ts b/app/src/shared/types.ts index 7748471..acd04a7 100644 --- a/app/src/shared/types.ts +++ b/app/src/shared/types.ts @@ -17,146 +17,3 @@ export type CommandResult = { } export type InputState = { key: string, shift: boolean, ctrl: boolean, meta: boolean, pressed: Set } - -export class GameContext { - constructor(public ctx: CanvasRenderingContext2D) { } - - get width() { return this.ctx.canvas.width } - get height() { return this.ctx.canvas.height } - - clear() { - this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height) - } - - circ(x: number, y: number, r: number, color = "black") { - const c = this.ctx - c.save() - c.beginPath() - c.strokeStyle = color - c.arc(x, y, r, 0, Math.PI * 2) - c.stroke() - c.restore() - } - - circfill(x: number, y: number, r: number, color = "black") { - const c = this.ctx - c.save() - c.beginPath() - c.fillStyle = color - c.arc(x, y, r, 0, Math.PI * 2) - c.fill() - c.restore() - } - - line(x0: number, y0: number, x1: number, y1: number, color = "black") { - const c = this.ctx - c.save() - c.beginPath() - c.strokeStyle = color - c.moveTo(x0, y0) - c.lineTo(x1, y1) - c.stroke() - c.restore() - } - - oval(x0: number, y0: number, x1: number, y1: number, color = "black") { - const c = this.ctx - const w = x1 - x0 - const h = y1 - y0 - c.save() - c.beginPath() - c.strokeStyle = color - c.ellipse(x0 + w / 2, y0 + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2) - c.stroke() - c.restore() - } - - ovalfill(x0: number, y0: number, x1: number, y1: number, color = "black") { - const c = this.ctx - const w = x1 - x0 - const h = y1 - y0 - c.save() - c.beginPath() - c.fillStyle = color - c.ellipse(x0 + w / 2, y0 + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2) - c.fill() - c.restore() - } - - rect(x0: number, y0: number, x1: number, y1: number, color = "black") { - const c = this.ctx - c.save() - c.beginPath() - c.strokeStyle = color - c.rect(x0, y0, x1 - x0, y1 - y0) - c.stroke() - c.restore() - } - - rectfill(x0: number, y0: number, x1: number, y1: number, color = "black") { - const c = this.ctx - c.save() - this.ctx.fillStyle = color - this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0) - c.restore() - } - - rrect(x: number, y: number, w: number, h: number, r: number, color = "black") { - const c = this.ctx - c.save() - c.beginPath() - c.strokeStyle = color - this.roundRectPath(x, y, w, h, r) - c.stroke() - c.restore() - } - - rrectfill(x: number, y: number, w: number, h: number, r: number, color = "black") { - const c = this.ctx - c.save() - c.beginPath() - c.fillStyle = color - this.roundRectPath(x, y, w, h, r) - c.fill() - c.restore() - } - - trianglefill(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, color = "black") { - return this.polygonfill( - [ - [x0, y0], - [x1, y1], - [x2, y2] - ], - color - ) - } - - polygonfill(points: [number, number][], color = "black") { - if (points.length < 3) return // need at least a triangle - const c = this.ctx - c.save() - c.beginPath() - c.fillStyle = color - c.moveTo(points[0]![0], points[0]![1]) - for (let i = 1; i < points.length; i++) { - c.lineTo(points[i]![0], points[i]![1]) - } - c.closePath() - c.fill() - c.restore() - } - - private roundRectPath(x: number, y: number, w: number, h: number, r: number) { - const c = this.ctx - c.moveTo(x + r, y) - c.lineTo(x + w - r, y) - c.quadraticCurveTo(x + w, y, x + w, y + r) - c.lineTo(x + w, y + h - r) - c.quadraticCurveTo(x + w, y + h, x + w - r, y + h) - c.lineTo(x + r, y + h) - c.quadraticCurveTo(x, y + h, x, y + h - r) - c.lineTo(x, y + r) - c.quadraticCurveTo(x, y, x + r, y) - } -} \ No newline at end of file