Initial commit

This commit is contained in:
Allen
2026-04-30 02:14:25 +00:00
commit 7090e6f01d
243 changed files with 115122 additions and 0 deletions
+724
View File
@@ -0,0 +1,724 @@
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);
});