nose-pluto/src/js/game.ts
Chris Wanstrath d95d2ec4b3 fix games
2025-10-02 13:23:07 -07:00

160 lines
3.8 KiB
TypeScript

import type { Message } from "../shared/types"
import { GameContext, type InputState } from "../shared/game"
import { focusInput } from "./focus"
import { $$ } from "./dom"
import { randomId } from "../shared/utils"
import { setStatus, addOutput, insert } from "./scrollback"
import { browserCommands } from "./commands"
import { sessionId } from "./session"
const FPS = 30
const HEIGHT = 540
const WIDTH = 980
type Game = { init?: () => void, update?: (delta: number, input: InputState) => void, draw?: (ctx: GameContext) => void }
let oldMode = "cinema"
let running = false
let canvas: HTMLCanvasElement
const pressedStack = new Set<string>()
let pressed: InputState = {
key: "",
shift: false,
ctrl: false,
meta: false,
pressed: pressedStack,
prevPressed: new Set(),
justPressed: new Set(),
justReleased: new Set(),
}
export async function handleGameStart(msg: Message) {
const msgId = msg.id as string
const name = msg.data as string
let game
try {
game = await import(`/source/${name}?session=${sessionId}`)
} catch (err: any) {
setStatus(msgId, "error")
addOutput(msgId, `Error: ${err.message ? err.message : err}`)
return
}
if (document.body.dataset.mode === "tall") {
browserCommands.mode?.()
oldMode = "tall"
}
canvas = createCanvas()
canvas.focus()
setStatus(msgId, "ok")
window.addEventListener("keydown", handleKeydown)
window.addEventListener("keyup", handleKeyup)
window.addEventListener("resize", resizeCanvas)
resizeCanvas()
gameLoop(new GameContext(canvas.getContext("2d")!), game)
}
function createCanvas(): HTMLCanvasElement {
const canvas = $$("canvas.game.active") as HTMLCanvasElement
canvas.id = randomId()
canvas.height = HEIGHT
canvas.width = WIDTH
canvas.tabIndex = 0
const main = document.querySelector("main")
main?.classList.add("game")
main?.parentNode?.insertBefore(canvas, main)
return canvas
}
function handleKeydown(e: KeyboardEvent) {
e.preventDefault()
if (e.key === "Escape" || (e.ctrlKey && e.key === "c")) {
endGame()
} else {
pressedStack.add(e.key)
pressed.key = e.key
pressed.ctrl = e.ctrlKey
pressed.shift = e.shiftKey
pressed.meta = e.metaKey
}
}
function handleKeyup(e: KeyboardEvent) {
pressedStack.delete(e.key)
if (pressedStack.size === 0) {
pressed.key = ""
pressed.ctrl = false
pressed.shift = false
pressed.meta = false
}
}
function updateInputState() {
pressed.justPressed = new Set([...pressed.pressed].filter(k => !pressed.prevPressed.has(k)))
pressed.justReleased = new Set([...pressed.prevPressed].filter(k => !pressed.pressed.has(k)))
pressed.prevPressed = new Set(pressed.pressed)
}
function resizeCanvas() {
const scale = Math.min(
window.innerWidth / 960,
window.innerHeight / 540
)
canvas.width = 960 * scale
canvas.height = 540 * scale
const ctx = canvas.getContext("2d")!
ctx.setTransform(scale, 0, 0, scale, 0, 0)
}
function gameLoop(ctx: GameContext, game: Game) {
running = true
let last = 0
if (game.init) game.init()
function loop(ts: number) {
if (!running) return
const delta = ts - last
if (delta >= 1000 / FPS) {
updateInputState()
if (game.update) game.update(delta, pressed)
if (game.draw) game.draw(ctx)
last = ts
}
requestAnimationFrame(loop)
}
requestAnimationFrame(loop)
}
function endGame() {
running = false
window.removeEventListener("keydown", handleKeydown)
window.removeEventListener("keyup", handleKeyup)
window.removeEventListener("resize", resizeCanvas)
if (oldMode === "tall") browserCommands.mode?.()
const main = document.querySelector("main")
main?.classList.remove("game")
canvas.classList.remove("active")
canvas.style.height = HEIGHT / 2 + "px"
canvas.style.width = WIDTH / 2 + "px"
const output = $$("li.output")
output.append(canvas)
insert(output)
focusInput()
}