From 32a62cfcb925225359a5c90761b795f2f737ebd2 Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Tue, 18 Aug 2026 00:08:28 -0400 Subject: Re-populate games --- 2026/games_submissions/ellg/game.js | 378 ------------------------------------ 1 file changed, 378 deletions(-) delete mode 100644 2026/games_submissions/ellg/game.js (limited to '2026/games_submissions/ellg/game.js') diff --git a/2026/games_submissions/ellg/game.js b/2026/games_submissions/ellg/game.js deleted file mode 100644 index a82d24b..0000000 --- a/2026/games_submissions/ellg/game.js +++ /dev/null @@ -1,378 +0,0 @@ -import { Vector } from "./vector.js"; - -const WIDTH = 240; -const HEIGHT = 160; -const STANDALONE = window.self === window.top; -const DIFFICULTY = 4; - -const canvas = document.getElementById("game"); - -const ctx = canvas.getContext("2d"); -ctx.imageSmoothingEnabled = false; - -function notifyHarness(msg) { - if (!STANDALONE) window.parent.postMessage(msg); -} - -const music = new Audio("./assets/song.mp3"); -music.loop = true; -music.volume = 0.5; - -function playMusic() { - music.currentTime = 0; - music.play().catch(() => { }); -} - -function stopMusic() { - music.pause(); -} - -export async function loadImage(url) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.onload = () => resolve(img); - img.onerror = reject; - img.src = url; - }); -}; - -export async function loadAssets() { - const [guy1, guy2, guy3, guy4, pot, table, beef1, beef2, hat, win, lose, hungry] = await Promise.all([ - loadImage("./assets/guy1.png"), - loadImage("./assets/guy2.png"), - loadImage("./assets/guy3.png"), - loadImage("./assets/guy4.png"), - loadImage("./assets/pot.png"), - loadImage("./assets/table.png"), - loadImage("./assets/beef1.png"), - loadImage("./assets/beef2.png"), - loadImage("./assets/chefhat.png"), - loadImage("./assets/win.png"), - loadImage("./assets/lose.png"), - loadImage("./assets/hungry.png"), - ]); - - return { - guy1, - guy2, - guy3, - guy4, - pot, - table, - beef1, - beef2, - hat, - win, - lose, - hungry - }; -}; - -let lastTime = 0; -let assets = {}; -let state = { - scene: null, -}; - -class Scene { - constructor() { } - - draw(dt) { } - update(dt) { } - handleInput(dt) { } - onClick(p) { } -} - -const END_SCREEN_TIME = 3; - -class EndScreen extends Scene { - constructor(win) { - super(); - - stopMusic(); - - this.win = win; - this.t = 0; - this.sent = false; - } - - update(dt) { - this.t += dt; - - if (!this.sent && this.t >= END_SCREEN_TIME) { - this.sent = true; - notifyHarness({ op: "done", win: this.win }); - } - } -} - -class WinScreen extends EndScreen { - constructor() { super(true) } - - draw() { - ctx.drawImage(assets.win, 0, 0); - } -} - -class GameOverScreen extends EndScreen { - constructor(endType = "death") { - super(false); - this.endType = endType; - } - - draw() { - ctx.drawImage(this.endType === "death" ? assets.lose : assets.hungry, 0, 0); - ctx.drawImage(assets.guy4, 50, 75); - } -} - -const POT = { x: 25, y: 90, size: 64 }; -const POT_CENTER = Vector.of([POT.x + POT.size / 2, POT.y + POT.size / 2]); -const COLLECT_TIME = 0.4; - -const BOUNDS = { x: 8, y: 8, w: 125, h: 96 }; - -class Icon { - constructor(img, pos, correct) { - this.img = img; - this.pos = pos; - this.size = 32; - this.correct = correct; - this.vel = new Vector(0, 0); - this.collect = null; - this.dead = false; - } - - static spawn(img, correct, speed) { - const icon = new Icon(img, Vector.of([0, 0]), correct); - icon.pos = Vector.of([ - BOUNDS.x + Math.random() * (BOUNDS.w - icon.size), - BOUNDS.y + Math.random() * (BOUNDS.h - icon.size), - ]); - const a = Math.random() * Math.PI * 2; - icon.vel = new Vector(Math.cos(a), Math.sin(a)).multiply(speed); - return icon; - } - - center() { return this.pos.add(new Vector(this.size / 2, this.size / 2)); } - - contains(p) { - return p.x >= this.pos.x && p.x <= this.pos.x + this.size && - p.y >= this.pos.y && p.y <= this.pos.y + this.size; - } - - startCollect() { - if (this.collect) return; - this.collect = { t: 0, fromCenter: this.center() }; - } - - update(dt) { - if (this.collect) { - this.collect.t += dt; - const k = Math.min(1, this.collect.t / COLLECT_TIME); - const e = k * k; - this._drawCenter = this.collect.fromCenter.add( - POT_CENTER.subtract(this.collect.fromCenter).multiply(e)); - this._scale = 1 - k; - if (k >= 1) this.dead = true; - return; - } - - this.pos = this.pos.add(this.vel.multiply(dt)); - const maxX = BOUNDS.x + BOUNDS.w - this.size; - const maxY = BOUNDS.y + BOUNDS.h - this.size; - if (this.pos.x < BOUNDS.x) { this.pos.x = BOUNDS.x; this.vel.x *= -1; } - if (this.pos.x > maxX) { this.pos.x = maxX; this.vel.x *= -1; } - if (this.pos.y < BOUNDS.y) { this.pos.y = BOUNDS.y; this.vel.y *= -1; } - if (this.pos.y > maxY) { this.pos.y = maxY; this.vel.y *= -1; } - } - - draw(ctx) { - const c = this._drawCenter ?? this.center(); - const s = this.size * (this._scale ?? 1); - ctx.drawImage( - this.img, - Math.round(c.x - s / 2), Math.round(c.y - s / 2), - Math.round(s), Math.round(s), - ); - } -} - -function difficultyConfig(raw) { - const K = 5; - const d = raw / (raw + K); - const lerp = (a, b) => a + (b - a) * d; - return { - correct: Math.round(lerp(3, 5)), - decoys: Math.round(lerp(3, 10)), - speed: lerp(20, 90), - timeLimit: lerp(8, 4), - }; -} - -function spawnIcons(cfg) { - const icons = []; - for (let i = 0; i < cfg.correct; i++) icons.push(Icon.spawn(assets.beef1, true, cfg.speed)); - for (let i = 0; i < cfg.decoys; i++) icons.push(Icon.spawn(assets.beef2, false, cfg.speed)); - return icons; -} - -class GameScreen extends Scene { - constructor(cfg) { - super(); - this.cfg = cfg; - this.timeTotal = cfg.timeLimit; - this.timeLeft = cfg.timeLimit; - this.state = { - guy: { - img: assets.guy2, - pos: Vector.of([125, 50]), - reactImg: null, - reactT: 0, - }, - icons: spawnIcons(cfg), - }; - } - - draw() { - const guy = this.state.guy; - - ctx.drawImage(guy.reactImg ?? guy.img, guy.pos.x, guy.pos.y); - ctx.drawImage(assets.hat, guy.pos.x + 45, guy.pos.y - 25, 50, 50); - ctx.drawImage(assets.table, 0, 50); - - for (const icon of this.state.icons) icon.draw(ctx); - - ctx.drawImage(assets.pot, POT.x, POT.y, POT.size, POT.size); - - const frac = this.timeLeft / this.timeTotal; - ctx.fillStyle = "#3a2a1a"; - ctx.fillRect(0, 0, WIDTH, 6); - ctx.fillStyle = frac < 0.25 ? "#e44" : "#f2c14e"; - ctx.fillRect(0, 0, Math.round(WIDTH * frac), 6); - } - - update(dt, ct) { - if (this.state.guy.reactT > 0) { - this.state.guy.reactT -= dt; - if (this.state.guy.reactT <= 0) this.state.guy.reactImg = null; - } - - this.timeLeft -= dt; - if (this.timeLeft <= 0) { - this.timeLeft = 0; - this.lose("hungry"); - return; - } - - const baseY = 50; - const amp = 4; - const speed = 0.01; - this.state.guy.pos.y = baseY + Math.round(Math.sin(ct * speed) * amp); - - for (const icon of this.state.icons) icon.update(dt); - this.state.icons = this.state.icons.filter(i => !i.dead); - } - - lose(endType = "death") { - state.scene = new GameOverScreen(endType); - } - - win() { - state.scene = new WinScreen(); - } - - onClick(p) { - for (let i = this.state.icons.length - 1; i >= 0; i--) { - const icon = this.state.icons[i]; - if (icon.collect) continue; - if (icon.contains(p)) { - if (!icon.correct) { this.lose() } - - icon.startCollect(); - - const correctLeft = this.state.icons.filter(x => x.correct && !x.collect).length; - if (correctLeft === 0) this.win() - - if (icon.correct) { - this.state.guy.reactImg = assets.guy3; - this.state.guy.reactT = 2; - }; - - return; - } - } - } -} - -async function init() { - assets = await loadAssets(); - notifyHarness({ op: "ready" }); -} - -function update(dt, ct) { - if (state.scene) { - state.scene.update(dt, Math.floor(ct)); - } -} - -function draw(dt) { - ctx.fillStyle = "#222"; - ctx.fillRect(0, 0, WIDTH, HEIGHT); - - if (state.scene) { - state.scene.draw(dt); - } -} - -function handleInput(dt) { - if (state.scene) { - state.scene.handleInput(dt); - } -} - -function main(currentTime) { - if (!lastTime) lastTime = currentTime; - const dt = Math.min((currentTime - lastTime) / 1000, 0.1); - - lastTime = currentTime; - - handleInput(dt); - update(dt, currentTime); - draw(dt); - - requestAnimationFrame(main); -} - -function startGame(difficulty = DIFFICULTY) { - state.scene = new GameScreen(difficultyConfig(difficulty)); - notifyHarness({ op: "started", verb: "make some chili!" }); - playMusic(); -} - -canvas.addEventListener("click", (ev) => { - if (music.paused && state.scene instanceof GameScreen) music.play().catch(() => { }); - - const r = canvas.getBoundingClientRect(); - const p = Vector.of([ - (ev.clientX - r.left) / r.width * WIDTH, - (ev.clientY - r.top) / r.height * HEIGHT, - ]); - state.scene?.onClick(p); -}); - -window.addEventListener("message", ev => { - switch (ev.data.op) { - case "start": - startGame(ev.data.difficulty ?? 0); - break; - default: console.log(`unknown event: ${ev}`); break; - } -}); - -await init(); - -requestAnimationFrame(main); - -if (STANDALONE) startGame(); - -- cgit v1.3.1