kgames

KGames — free keyboard games for kids
git clone https://www.keyboard.games/code/kgames.git
Log | Files | Refs | README | LICENSE

commit 17733c85aa2ea0577cff75b40c6e46ee3e4cccb5
parent df1c7123b6e429d644768b17e155bcacf23697da
Author: Kyle Barlow <kb@kylebarlow.com>
Date:   Wed, 22 Apr 2026 14:29:07 -0700

Scale game up 5x and zoom out camera to improve perceived rendering framerate. Add settings gear button for speed menu.

Diffstat:
Mgames/fire-truck/game.js | 33+++++++++++++++++++++------------
Mgames/fire-truck/index.html | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mtests/browser/runner.js | 5++++-
3 files changed, 79 insertions(+), 26 deletions(-)

diff --git a/games/fire-truck/game.js b/games/fire-truck/game.js @@ -1,13 +1,14 @@ const FT = window.FireTruckLib; -const CELL_SIZE = 96; -const ROAD_WIDTH = 44; -const BASE_SPEED = 34; -const MAX_SPEED = 44; -const ACCEL = 52; -const BRAKE = 64; +const SCALE = 5; +const CELL_SIZE = 96 * SCALE; +const ROAD_WIDTH = 44 * SCALE; +const BASE_SPEED = 170; +const MAX_SPEED = 220; +const ACCEL = 260; +const BRAKE = 320; const PROMPT_TRIGGER_DIST = CELL_SIZE * 0.72; -const STOP_LINE_DIST = 6; +const STOP_LINE_DIST = 6 * SCALE; const AUTO_FAIL_GRACE_MS = 4500; class FireTruckScene extends Phaser.Scene { @@ -78,9 +79,15 @@ class FireTruckScene extends Phaser.Scene { }).setOrigin(0.5).setScrollFactor(0).setDepth(50); this.statusText.setAlpha(0.88); + this.uiCamera = this.cameras.add(0, 0, this.scale.width, this.scale.height); + this.cameras.main.setZoom(1 / SCALE); + this.renderCity(); this.createTruck(); + this.uiCamera.ignore([this.roadGraphics, this.buildingGraphics, this.truck]); + this.cameras.main.ignore([this.overlay, this.successOverlay, this.promptText, this.statusText]); + this.currentStep = this.route[0]; const start = this.worldPoint(-1, 0); this.segmentEnd = this.worldPoint(this.currentStep.x, this.currentStep.y); @@ -138,9 +145,9 @@ class FireTruckScene extends Phaser.Scene { const py = y * CELL_SIZE - CELL_SIZE * 0.38; const color = palette[Math.abs((x * 13 + y * 17) % palette.length)]; buildings.fillStyle(color, 1); - buildings.fillRoundedRect(px, py, CELL_SIZE * 0.76, CELL_SIZE * 0.76, 10); + buildings.fillRoundedRect(px, py, CELL_SIZE * 0.76, CELL_SIZE * 0.76, 10 * SCALE); buildings.fillStyle(0xffffff, 0.16); - buildings.fillRect(px + 8, py + 8, CELL_SIZE * 0.3, 10); + buildings.fillRect(px + 8 * SCALE, py + 8 * SCALE, CELL_SIZE * 0.3, 10 * SCALE); } } @@ -154,8 +161,8 @@ class FireTruckScene extends Phaser.Scene { if (cell.exits.W) road.fillRect(center.x - CELL_SIZE / 2, center.y - ROAD_WIDTH / 2, CELL_SIZE / 2, ROAD_WIDTH); road.fillStyle(0xfff4b1, 0.92); - if (cell.exits.N && cell.exits.S) road.fillRect(center.x - 3, center.y - CELL_SIZE / 2 + 8, 6, CELL_SIZE - 16); - if (cell.exits.E && cell.exits.W) road.fillRect(center.x - CELL_SIZE / 2 + 8, center.y - 3, CELL_SIZE - 16, 6); + if (cell.exits.N && cell.exits.S) road.fillRect(center.x - 3 * SCALE, center.y - CELL_SIZE / 2 + 8 * SCALE, 6 * SCALE, CELL_SIZE - 16 * SCALE); + if (cell.exits.E && cell.exits.W) road.fillRect(center.x - CELL_SIZE / 2 + 8 * SCALE, center.y - 3 * SCALE, CELL_SIZE - 16 * SCALE, 6 * SCALE); }); } @@ -166,6 +173,7 @@ class FireTruckScene extends Phaser.Scene { const light = this.add.rectangle(-14, 0, 8, 8, 0x1d7cf2, 1); this.truck = this.add.container(0, 0, [body, cab, ladder, light]); this.truck.setDepth(35); + this.truck.setScale(SCALE); } onResize(gameSize) { @@ -173,6 +181,7 @@ class FireTruckScene extends Phaser.Scene { this.successOverlay.setSize(gameSize.width, gameSize.height); this.promptText.setPosition(gameSize.width / 2, gameSize.height - 74); this.statusText.setPosition(gameSize.width / 2, gameSize.height - 34); + if (this.uiCamera) this.uiCamera.setSize(gameSize.width, gameSize.height); } updateTargetSpeed() { @@ -304,7 +313,7 @@ class FireTruckScene extends Phaser.Scene { } const remaining = Math.hypot(this.segmentEnd.x - this.truck.x, this.segmentEnd.y - this.truck.y); - if (remaining <= 0.8) { + if (remaining <= 0.8 * SCALE) { this.truck.setPosition(this.segmentEnd.x, this.segmentEnd.y); this.advancePhase(); } diff --git a/games/fire-truck/index.html b/games/fire-truck/index.html @@ -51,37 +51,78 @@ } #hud .title { font-size: 1.2rem; font-weight: 600; } #hud .hint { font-size: 0.9rem; opacity: 0.78; } - #settings { - margin-top: 0.4rem; - font-size: 0.9rem; + #settings-btn { + position: fixed; + top: 0.6rem; + right: 1rem; + z-index: 30; + background: rgba(255, 255, 255, 0.72); + border-radius: 50%; + width: 2.6rem; + height: 2.6rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 1.4rem; + user-select: none; + box-shadow: 0 4px 12px rgba(0,0,0,0.1); + transition: background 0.1s; + } + #settings-btn:hover { background: rgba(255, 255, 255, 0.9); } + #settings-panel { + position: fixed; + top: 3.6rem; + right: 1rem; + z-index: 30; + background: rgba(255, 255, 255, 0.92); + border-radius: 12px; + padding: 1rem; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); + display: none; + font-size: 0.95rem; + color: #2e372d; + } + #settings-panel.open { display: block; } + #settings-panel label { margin-right: 0.5rem; font-weight: 600; } + #speed-select { + padding: 0.25rem 0.5rem; + font-family: inherit; + border-radius: 6px; + border: 1px solid #ccc; } #game-container { flex: 1; width: 100%; height: 100%; } </style> </head> <body> <nav><a href="../../index.html">&larr; Back to KGames</a></nav> + <div id="settings-btn">⚙️</div> + <div id="settings-panel"> + <label for="speed-select">Speed:</label> + <select id="speed-select"> + <option value="0.5">0.5x</option> + <option value="1" selected>1x (Default)</option> + <option value="2">2x</option> + <option value="3">3x</option> + <option value="5">5x</option> + </select> + </div> <div id="hud"> <div class="title">Arrow Key Fire Truck</div> <div class="hint">Left arrow = left, up arrow = straight, right arrow = right</div> - <div id="settings"> - <label for="speed-select">Speed:</label> - <select id="speed-select"> - <option value="0.5">0.5x</option> - <option value="1" selected>1x (Default)</option> - <option value="2">2x</option> - <option value="3">3x</option> - <option value="5">5x</option> - </select> - </div> </div> <div id="game-container"></div> <script> if (typeof window.GAME_SPEED_MULTIPLIER === 'undefined') { window.GAME_SPEED_MULTIPLIER = 1.0; } + document.getElementById('settings-btn').addEventListener('click', () => { + document.getElementById('settings-panel').classList.toggle('open'); + }); document.getElementById('speed-select').addEventListener('change', (e) => { window.GAME_SPEED_MULTIPLIER = parseFloat(e.target.value); if (window.__FT_SCENE__) window.__FT_SCENE__.updateTargetSpeed(); + e.target.blur(); }); </script> <script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script> diff --git a/tests/browser/runner.js b/tests/browser/runner.js @@ -14,7 +14,7 @@ async function main() { async function test(name, fn) { const page = await browser.newPage(); // Speed up fire-truck tests - await page.addInitScript(() => { window.GAME_SPEED_MULTIPLIER = 2.0; }); + await page.addInitScript(() => { window.GAME_SPEED_MULTIPLIER = 8.0; }); const errors = []; page.on('console', m => { if (m.type() === 'error') errors.push('[console] ' + m.text()); }); page.on('pageerror', e => { errors.push('[pageerror] ' + e.message); }); @@ -135,6 +135,7 @@ async function main() { await test('fire-truck: prompt appears before an intersection', async (page, url) => { await page.goto(`${url}/games/fire-truck/`, { waitUntil: 'domcontentloaded' }); + await page.evaluate(() => { window.__FT_SCENE__.truck.setPosition(window.__FT_SCENE__.segmentEnd.x - 500, window.__FT_SCENE__.segmentEnd.y); }); await page.waitForFunction(() => window.__FT_SCENE__ && !!window.__FT_SCENE__.promptDir, null, { timeout: 25000 }); const promptDir = await page.evaluate(() => window.__FT_SCENE__.promptDir); if (!['left', 'right', 'straight'].includes(promptDir)) throw new Error(`Unexpected prompt ${promptDir}`); @@ -142,6 +143,7 @@ async function main() { await test('fire-truck: wrong arrow stops the truck', async (page, url) => { await page.goto(`${url}/games/fire-truck/`, { waitUntil: 'domcontentloaded' }); + await page.evaluate(() => { window.__FT_SCENE__.truck.setPosition(window.__FT_SCENE__.segmentEnd.x - 500, window.__FT_SCENE__.segmentEnd.y); }); await page.waitForFunction(() => window.__FT_SCENE__ && !!window.__FT_SCENE__.promptDir, null, { timeout: 25000 }); // Freeze the truck so it doesn't cross the intersection while playwright is IPCing await page.evaluate(() => { window.__FT_SCENE__.targetSpeed = 0; window.__FT_SCENE__.speed = 0; window.__FT_SCENE__.lastStepTime = performance.now(); }); @@ -156,6 +158,7 @@ async function main() { await test('fire-truck: correct arrow after failure resumes movement', async (page, url) => { await page.goto(`${url}/games/fire-truck/`, { waitUntil: 'domcontentloaded' }); + await page.evaluate(() => { window.__FT_SCENE__.truck.setPosition(window.__FT_SCENE__.segmentEnd.x - 500, window.__FT_SCENE__.segmentEnd.y); }); await page.waitForFunction(() => window.__FT_SCENE__ && !!window.__FT_SCENE__.promptDir, null, { timeout: 25000 }); await page.evaluate(() => { window.__FT_SCENE__.targetSpeed = 0; window.__FT_SCENE__.speed = 0; window.__FT_SCENE__.lastStepTime = performance.now(); }); const promptDir = await page.evaluate(() => window.__FT_SCENE__.promptDir);