require('dotenv').config(); const { pool } = require('../src/models/db'); const { generateGame } = require('../src/utils/claude'); const games = [ { id: 14, title: "Snake", prompt: `Classic Snake game - VERY EASY for 8-year-olds. Must be playable for 1+ minute before getting hard. IMPORTANT - NO AUDIO TOGGLE BUTTONS: - Do NOT add any sound toggle buttons to the game - Parent page controls audio via window.setMusicEnabled() and window.setSfxEnabled() - Just expose these functions, don't create UI for them CRITICAL - CANVAS SETUP: \`\`\`javascript const canvas = document.createElement('canvas'); document.body.appendChild(canvas); document.body.style.margin = '0'; document.body.style.overflow = 'hidden'; document.body.style.background = '#111'; function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resize(); window.addEventListener('resize', resize); const ctx = canvas.getContext('2d'); \`\`\` GAME SPEED - VERY IMPORTANT: - Initial update interval: 280ms (VERY SLOW - easy for kids) - Only speed up after score >= 100 (about 10 apples) - Minimum interval: 150ms (never faster) - Speed formula: Math.max(150, 280 - Math.floor(score / 20) * 10) GAME MECHANICS: - Grid: 15x15 cells (large cells, easy to see) - Snake starts with 3 segments in center, moving RIGHT - WRAP AROUND edges (no wall death - forgiving) - Game over ONLY on self-collision - Apple = +10 points CONTROLS: - Arrow keys for desktop - Swipe detection (touchstart/touchend position difference) - Semi-transparent D-pad at bottom (70px buttons) PAUSE SUPPORT: \`\`\`javascript let paused = false; window.pauseGame = () => { paused = true; }; window.resumeGame = () => { paused = false; }; // In game loop: if (paused) return; \`\`\` NES-STYLE MUSIC - Multi-channel chiptune: \`\`\`javascript let audioCtx, musicEnabled = true, sfxEnabled = true; let musicPlaying = false; function initAudio() { if (audioCtx) return; audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } // Two-channel NES music: melody + bass const melody = [ {note: 392, dur: 0.15}, {note: 440, dur: 0.15}, {note: 494, dur: 0.15}, {note: 523, dur: 0.3}, {note: 494, dur: 0.15}, {note: 440, dur: 0.15}, {note: 392, dur: 0.3}, {note: 330, dur: 0.3} ]; const bass = [196, 196, 220, 220, 196, 196, 165, 165]; let melodyIdx = 0, bassIdx = 0; function playMelody() { if (!musicEnabled || !audioCtx || paused) return; const n = melody[melodyIdx]; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'square'; osc.frequency.value = n.note; gain.gain.setValueAtTime(0.1, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + n.dur); osc.connect(gain).connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + n.dur); melodyIdx = (melodyIdx + 1) % melody.length; } function playBass() { if (!musicEnabled || !audioCtx || paused) return; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'triangle'; osc.frequency.value = bass[bassIdx]; gain.gain.setValueAtTime(0.08, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2); osc.connect(gain).connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + 0.2); bassIdx = (bassIdx + 1) % bass.length; } let melodyInt, bassInt; function startMusic() { if (musicPlaying) return; musicPlaying = true; melodyInt = setInterval(playMelody, 200); bassInt = setInterval(playBass, 400); } function stopMusic() { musicPlaying = false; clearInterval(melodyInt); clearInterval(bassInt); } window.setMusicEnabled = (v) => { musicEnabled = v; v ? startMusic() : stopMusic(); }; window.setSfxEnabled = (v) => { sfxEnabled = v; }; \`\`\` SOUND EFFECTS (when sfxEnabled): - Eat: 880Hz square, 50ms - Game over: 220Hz descending to 110Hz, 400ms VISUALS: - Dark background (#111) - Bright green snake (#22c55e) with darker head (#16a34a) - Red apple (#ef4444) - White score at top center - D-pad: semi-transparent white arrows on dark circles` }, { id: 18, title: "Space Invaders", prompt: `Space Invaders - VERY EASY for 8-year-olds. Must be playable for 1+ minute. IMPORTANT - NO AUDIO TOGGLE BUTTONS: - Do NOT add any sound toggle buttons to the game - Parent page controls audio via window.setMusicEnabled() and window.setSfxEnabled() - Just expose these functions, don't create UI for them CRITICAL - CANVAS SETUP: \`\`\`javascript const canvas = document.createElement('canvas'); document.body.appendChild(canvas); document.body.style.margin = '0'; document.body.style.overflow = 'hidden'; document.body.style.background = '#000'; function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resize(); window.addEventListener('resize', resize); const ctx = canvas.getContext('2d'); \`\`\` ALIEN INITIALIZATION - CRITICAL (do this AFTER canvas is sized): \`\`\`javascript let aliens = []; let player = { x: 0, y: 0, width: 50, height: 30 }; let gameStarted = false; function initGame() { // Player at bottom player.x = canvas.width / 2; player.y = canvas.height - 70; // Create aliens - 3 rows x 6 columns aliens = []; const startX = canvas.width * 0.2; const startY = 80; for (let row = 0; row < 3; row++) { for (let col = 0; col < 6; col++) { aliens.push({ x: startX + col * 60, y: startY + row * 50, alive: true, width: 40, height: 30 }); } } alienDir = 1; alienSpeed = 0.3; // VERY slow gameStarted = true; } // Call initGame when first interaction happens canvas.addEventListener('click', () => { if (!gameStarted) { initAudio(); initGame(); } }); canvas.addEventListener('touchstart', () => { if (!gameStarted) { initAudio(); initGame(); } }); \`\`\` ALIEN MOVEMENT - MUST move sideways, NOT fall: \`\`\`javascript let alienDir = 1; let alienSpeed = 0.3; function updateAliens() { if (!gameStarted || paused) return; let hitEdge = false; let minX = canvas.width, maxX = 0; aliens.forEach(a => { if (!a.alive) return; a.x += alienDir * alienSpeed; minX = Math.min(minX, a.x); maxX = Math.max(maxX, a.x + a.width); }); if (minX < 20 || maxX > canvas.width - 20) { hitEdge = true; } if (hitEdge) { alienDir *= -1; aliens.forEach(a => { if (a.alive) a.y += 20; }); } } \`\`\` MOBILE CONTROLS - Slide to move, tap to fire: \`\`\`javascript let touchStartX = 0; let touchMoved = false; let lastTouchX = 0; canvas.addEventListener('touchstart', (e) => { e.preventDefault(); if (!gameStarted) { initAudio(); initGame(); return; } touchStartX = e.touches[0].clientX; lastTouchX = touchStartX; touchMoved = false; }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const touchX = e.touches[0].clientX; const dx = touchX - lastTouchX; if (Math.abs(touchX - touchStartX) > 10) touchMoved = true; player.x = Math.max(25, Math.min(canvas.width - 25, player.x + dx)); lastTouchX = touchX; }); canvas.addEventListener('touchend', (e) => { e.preventDefault(); if (!touchMoved && gameStarted) { fireBullet(); } }); \`\`\` DESKTOP: Arrow keys to move, Space to fire PAUSE SUPPORT: \`\`\`javascript let paused = false; window.pauseGame = () => { paused = true; }; window.resumeGame = () => { paused = false; }; \`\`\` DIFFICULTY: - Aliens shoot VERY rarely: Math.random() < 0.001 per frame per alien - Player has 5 lives - Bullets move slowly (4px/frame) - 10 points per alien NES-STYLE MUSIC - Space march: \`\`\`javascript let audioCtx, musicEnabled = true, sfxEnabled = true, musicPlaying = false; function initAudio() { if (audioCtx) return; audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } const marchNotes = [82, 98, 82, 98]; // E2, G2 pattern let marchIdx = 0, marchInt; function playMarch() { if (!musicEnabled || !audioCtx || paused) return; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'square'; osc.frequency.value = marchNotes[marchIdx]; gain.gain.setValueAtTime(0.12, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.08); osc.connect(gain).connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + 0.08); marchIdx = (marchIdx + 1) % marchNotes.length; // Also play higher accent every 4 notes if (marchIdx === 0) { const osc2 = audioCtx.createOscillator(); const gain2 = audioCtx.createGain(); osc2.type = 'square'; osc2.frequency.value = 165; gain2.gain.setValueAtTime(0.06, audioCtx.currentTime); gain2.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.05); osc2.connect(gain2).connect(audioCtx.destination); osc2.start(); osc2.stop(audioCtx.currentTime + 0.05); } } function startMusic() { if (musicPlaying) return; musicPlaying = true; marchInt = setInterval(playMarch, 500); } function stopMusic() { musicPlaying = false; clearInterval(marchInt); } window.setMusicEnabled = (v) => { musicEnabled = v; v ? startMusic() : stopMusic(); }; window.setSfxEnabled = (v) => { sfxEnabled = v; }; \`\`\` SOUND EFFECTS: - Fire: 1200Hz, 30ms square - Alien hit: noise burst 50ms - Player hit: 80Hz, 200ms VISUALS: - Black background - Green triangular ship at bottom - Colorful aliens (red/yellow/cyan by row) - Show "TAP TO START" before game begins - Lives as small ships bottom-left - Score top-left` }, { id: 20, title: "Flappy Bird", prompt: `Flappy Bird - VERY EASY for 8-year-olds. Playable for 1+ minute before hard. IMPORTANT - NO AUDIO TOGGLE BUTTONS: - Do NOT add any sound toggle buttons to the game - Parent page controls audio via window.setMusicEnabled() and window.setSfxEnabled() - Just expose these functions, don't create UI for them CRITICAL - CANVAS SETUP: \`\`\`javascript const canvas = document.createElement('canvas'); document.body.appendChild(canvas); document.body.style.margin = '0'; document.body.style.overflow = 'hidden'; function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resize(); window.addEventListener('resize', resize); const ctx = canvas.getContext('2d'); \`\`\` PROGRESSIVE DIFFICULTY - VERY GRADUAL: \`\`\`javascript function getGapSize() { // Start HUGE (50% of screen), shrink SLOWLY const baseGap = canvas.height * 0.5; const minGap = canvas.height * 0.25; // Only shrink after 60 seconds worth of points (about 30 pipes) const reduction = Math.min(score, 30) * 5; return Math.max(minGap, baseGap - reduction); } function getPipeSpeed() { // Start slow (2), max out at 4 return 2 + Math.min(score * 0.05, 2); } function getGravity() { return 0.35; // Gentle gravity throughout } \`\`\` GAME MECHANICS: - Bird at x = canvas.width * 0.3 - Gravity: 0.35 per frame (gentle) - Flap: velocity = -7 (strong boost) - Pipes spawn every 180 frames - Gap position: random but never too close to top/bottom - Forgiving hitbox: 5px smaller than visual on each side CONTROLS: \`\`\`javascript let gameOver = false, gameStarted = false; let bird = { x: 0, y: 0, vy: 0, radius: 18 }; function flap() { initAudio(); if (gameOver) { resetGame(); return; } if (!gameStarted) { gameStarted = true; startMusic(); } bird.vy = -7; if (sfxEnabled) playFlap(); } canvas.addEventListener('click', flap); canvas.addEventListener('touchstart', (e) => { e.preventDefault(); flap(); }); document.addEventListener('keydown', (e) => { if (e.code === 'Space' || e.key === ' ') { e.preventDefault(); flap(); } }); \`\`\` PAUSE SUPPORT: \`\`\`javascript let paused = false; window.pauseGame = () => { paused = true; stopMusic(); }; window.resumeGame = () => { paused = false; if (gameStarted && !gameOver) startMusic(); }; \`\`\` NES-STYLE MUSIC - Happy chiptune: \`\`\`javascript let audioCtx, musicEnabled = true, sfxEnabled = true, musicPlaying = false; function initAudio() { if (audioCtx) return; audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } // Happy melody in C major const melody = [ {n: 523, d: 0.12}, {n: 587, d: 0.12}, {n: 659, d: 0.12}, {n: 784, d: 0.24}, {n: 659, d: 0.12}, {n: 587, d: 0.12}, {n: 523, d: 0.24}, {n: 440, d: 0.12}, {n: 523, d: 0.12}, {n: 587, d: 0.12}, {n: 523, d: 0.24} ]; const bass = [262, 262, 330, 330, 262, 262, 220, 220, 262, 262, 262]; let mIdx = 0, melodyInt, bassInt; function playMelodyNote() { if (!musicEnabled || !audioCtx || paused) return; const m = melody[mIdx]; const osc = audioCtx.createOscillator(); const g = audioCtx.createGain(); osc.type = 'square'; osc.frequency.value = m.n; g.gain.setValueAtTime(0.08, audioCtx.currentTime); g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + m.d * 0.9); osc.connect(g).connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + m.d); mIdx = (mIdx + 1) % melody.length; } function playBassNote() { if (!musicEnabled || !audioCtx || paused) return; const osc = audioCtx.createOscillator(); const g = audioCtx.createGain(); osc.type = 'triangle'; osc.frequency.value = bass[mIdx % bass.length]; g.gain.setValueAtTime(0.06, audioCtx.currentTime); g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15); osc.connect(g).connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + 0.15); } function startMusic() { if (musicPlaying) return; musicPlaying = true; melodyInt = setInterval(playMelodyNote, 150); bassInt = setInterval(playBassNote, 300); } function stopMusic() { musicPlaying = false; clearInterval(melodyInt); clearInterval(bassInt); } window.setMusicEnabled = (v) => { musicEnabled = v; if(gameStarted && !gameOver) v ? startMusic() : stopMusic(); }; window.setSfxEnabled = (v) => { sfxEnabled = v; }; function playFlap() { if (!sfxEnabled || !audioCtx) return; // Quick noise whoosh const bufferSize = audioCtx.sampleRate * 0.03; const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * (1 - i/bufferSize); const src = audioCtx.createBufferSource(); src.buffer = buffer; const g = audioCtx.createGain(); g.gain.value = 0.15; src.connect(g).connect(audioCtx.destination); src.start(); } \`\`\` VISUALS: - Sky blue gradient (#87CEEB to #E0F4FF) - Yellow bird (circle) with orange beak, white eye - Green pipes (#22c55e) with darker caps (#16a34a) - Brown ground (#8B4513) at bottom, 40px tall - Large white score at top center - "Tap to Start" text before game - "Game Over - Tap to Retry" when dead` }, { id: 22, title: "Minesweeper", prompt: `Minesweeper - EASY for 8-year-olds. DOM-based (not canvas). IMPORTANT - NO AUDIO TOGGLE BUTTONS: - Do NOT add any sound toggle buttons to the game - Parent page controls audio via window.setMusicEnabled() and window.setSfxEnabled() - Just expose these functions, don't create UI for them LAYOUT SETUP: \`\`\`javascript document.body.style.cssText = 'margin:0;background:#1e293b;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:15px;box-sizing:border-box;font-family:system-ui,sans-serif;'; const GRID_SIZE = 8; const MINES = 8; const cellSize = Math.min(48, Math.floor((Math.min(window.innerWidth, window.innerHeight) - 60) / GRID_SIZE)); // Header const header = document.createElement('div'); header.style.cssText = 'display:flex;gap:15px;margin-bottom:12px;align-items:center;color:white;'; document.body.appendChild(header); // Mine counter const mineCount = document.createElement('span'); mineCount.style.cssText = 'font-size:1.2rem;font-weight:bold;min-width:40px;'; mineCount.textContent = '💣 ' + MINES; header.appendChild(mineCount); // Reset button const resetBtn = document.createElement('button'); resetBtn.style.cssText = 'font-size:1.5rem;padding:5px 15px;border:none;border-radius:8px;cursor:pointer;background:#fbbf24;'; resetBtn.textContent = '😊'; resetBtn.onclick = resetGame; header.appendChild(resetBtn); // Timer const timerEl = document.createElement('span'); timerEl.style.cssText = 'font-size:1.2rem;font-weight:bold;min-width:50px;'; timerEl.textContent = '⏱ 0'; header.appendChild(timerEl); // Grid const grid = document.createElement('div'); grid.style.cssText = \`display:grid;grid-template-columns:repeat(\${GRID_SIZE},\${cellSize}px);gap:3px;background:#334155;padding:10px;border-radius:10px;\`; document.body.appendChild(grid); \`\`\` CELL CREATION: \`\`\`javascript const cells = []; const cellData = []; // {mine, revealed, flagged, adjacentMines} function createCells() { grid.innerHTML = ''; cells.length = 0; cellData.length = 0; for (let i = 0; i < GRID_SIZE * GRID_SIZE; i++) { const cell = document.createElement('div'); cell.style.cssText = \` width:\${cellSize}px;height:\${cellSize}px; background:#64748b;border-radius:6px; display:flex;align-items:center;justify-content:center; font-size:\${cellSize * 0.55}px;font-weight:bold; cursor:pointer;user-select:none; transition:background 0.1s; \`; cellData.push({ mine: false, revealed: false, flagged: false, adjacentMines: 0 }); // MOBILE LONG PRESS FOR FLAG let pressTimer = null; let longPressed = false; cell.addEventListener('touchstart', (e) => { e.preventDefault(); longPressed = false; cell.style.background = '#475569'; pressTimer = setTimeout(() => { longPressed = true; toggleFlag(i); cell.style.background = cellData[i].revealed ? '#e2e8f0' : '#64748b'; }, 400); }); cell.addEventListener('touchend', (e) => { e.preventDefault(); clearTimeout(pressTimer); cell.style.background = cellData[i].revealed ? '#e2e8f0' : '#64748b'; if (!longPressed && !gameOver) { revealCell(i); } }); cell.addEventListener('touchmove', () => { clearTimeout(pressTimer); cell.style.background = cellData[i].revealed ? '#e2e8f0' : '#64748b'; }); // Desktop cell.addEventListener('click', () => revealCell(i)); cell.addEventListener('contextmenu', (e) => { e.preventDefault(); toggleFlag(i); }); cells.push(cell); grid.appendChild(cell); } } \`\`\` GAME LOGIC: - First click + 8 neighbors always safe (place mines after) - Flood fill for empty cells - Numbers colored: 1=blue, 2=green, 3=red, 4=purple, 5=maroon, 6=teal PAUSE SUPPORT: \`\`\`javascript let paused = false; window.pauseGame = () => { paused = true; stopMusic(); }; window.resumeGame = () => { paused = false; startMusic(); }; \`\`\` NES-STYLE MUSIC - Calm puzzle: \`\`\`javascript let audioCtx, musicEnabled = true, sfxEnabled = true, musicPlaying = false; function initAudio() { if (audioCtx) return; audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } // Calm arpeggio pattern const notes = [262, 330, 392, 330, 262, 294, 349, 294]; let noteIdx = 0, musicInt; function playNote() { if (!musicEnabled || !audioCtx || paused || gameOver) return; const osc = audioCtx.createOscillator(); const g = audioCtx.createGain(); osc.type = 'sine'; osc.frequency.value = notes[noteIdx]; g.gain.setValueAtTime(0.07, audioCtx.currentTime); g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.25); osc.connect(g).connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + 0.25); noteIdx = (noteIdx + 1) % notes.length; } function startMusic() { if (musicPlaying) return; musicPlaying = true; musicInt = setInterval(playNote, 350); } function stopMusic() { musicPlaying = false; clearInterval(musicInt); } window.setMusicEnabled = (v) => { musicEnabled = v; v ? startMusic() : stopMusic(); }; window.setSfxEnabled = (v) => { sfxEnabled = v; }; \`\`\` SOUND EFFECTS: - Reveal: 600Hz click, 20ms - Flag: 300Hz, 40ms - Mine: noise explosion 200ms - Win: ascending C-E-G-C arpeggio VISUALS: - Dark slate background - Gray unrevealed (#64748b) - Light revealed (#e2e8f0) - 🚩 for flags, 💣 for mines - 😊 normal, 😎 win, 😵 lose for reset button` } ]; async function regenerateGame(g) { console.log(`\nRegenerating: ${g.title}...`); try { const result = await generateGame(g.prompt); if (!result.success) { console.log(` FAILED: ${result.error}`); return false; } await pool.query('UPDATE games SET game_code = $1, prompt = $2 WHERE id = $3', [result.code, g.prompt, g.id]); console.log(` SUCCESS`); return true; } catch (e) { console.log(` ERROR: ${e.message}`); return false; } } async function main() { console.log('='.repeat(50)); console.log('REGENERATING 4 GAMES - v3'); console.log('Slow snake, fixed space invaders, better music'); console.log('='.repeat(50)); let ok = 0; for (const g of games) { if (await regenerateGame(g)) ok++; await new Promise(r => setTimeout(r, 3000)); } console.log('\n' + '='.repeat(50)); console.log(`COMPLETE: ${ok}/4 successful`); console.log('='.repeat(50)); await pool.end(); process.exit(ok === 4 ? 0 : 1); } main().catch(err => { console.error('Fatal:', err); process.exit(1); });