Files
gijs_pong/.planning/phases/03-complete-experience/03-01-PLAN.md

15 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
03-complete-experience 01 execute 1
index.html
true
SCRN-01
SCRN-02
SCRN-03
SCRN-04
truths artifacts key_links
Opening the game shows a title screen with glowing 'SUPER PONG NEXT GEN' title, neon accent color, and a menu with Play and Settings
Arrow keys navigate menu items; Enter/Space confirms; Escape/Backspace goes back
Play navigates to mode select (Solo vs AI / 2-Player), then difficulty select for AI mode
Settings screen allows changing AI difficulty, sound on/off, and color scheme — changes take effect immediately
Completing a match shows the game over screen with winner + full score (e.g. PLAYER 1 WINS 7 4) and Play Again / Main Menu options
All screens use the same neon palette — ball, paddles, text accents, and glow all update when palette changes
path provides contains
index.html Palette system, new gameStates (title/settings), neon rendering for all screens GameConfig.PALETTES, GameState.activePalette, GameState.soundEnabled, GameState.selectedMenu
from to via pattern
Input._handleKeyDown GameState.gameState ArrowUp/ArrowDown/Enter/Escape modify selectedMenu and gameState ArrowUp|ArrowDown|Enter|Escape|Backspace
from to via pattern
GameLoop.main() render switch GameState.activePalette All ctx.fillStyle calls reference activePalette.accent activePalette.accent
from to via pattern
GameState.activePalette GameConfig.PALETTES Settings screen sets activePalette = GameConfig.PALETTES[name] PALETTES.
Transform all screens to the neon arcade aesthetic and build proper title/settings navigation.

Purpose: Replace Phase 2's plain white temporary screens with a coherent visual identity. Add the title screen (first thing player sees) and settings screen (palette, difficulty, sound). Wire arrow-key menu navigation. Overhaul game over to show full score format.

Output: index.html with palette system, 5 neon-styled game states (title, settings, modeSelect, diffSelect, gameover), and complete keyboard navigation flow.

<execution_context> @/home/dabit/.claude/get-shit-done/workflows/execute-plan.md @/home/dabit/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-complete-experience/03-CONTEXT.md @.planning/phases/03-complete-experience/03-RESEARCH.md

GameConfig (current structure):

const GameConfig = {
  initialBallSpeed: 220,
  speedIncrement: 18,
  paddleSpeed: 400,
  WIN_SCORE: 7,
  AI_EASY:   { speed: 200, reactionDelay: 0.3,  errorMargin: 20 },
  AI_MEDIUM: { speed: 320, reactionDelay: 0.1,  errorMargin: 5  },
  AI_HARD:   { speed: 400, reactionDelay: 0.05, errorMargin: 2  }
};
// ADD: PALETTES object with MAGENTA, CYAN, LIME, WHITE presets
// ADD: PALETTE_KEYS array for cycling ['MAGENTA','CYAN','LIME','WHITE']

GameState (current structure):

const GameState = {
  paddle1: { x,y,width:12,height:80,speed:400,color:'#fff' },
  ball:    { x,y,radius:8,vx,vy,speed,color:'#fff' },
  paddle2: { x,y,width:12,height:80,speed:400,color:'#fff' },
  score1: 0, score2: 0,
  mode: null,
  difficulty: 'medium',
  gameState: 'modeSelect',  // Change initial value to 'title'
  winner: null
};
// ADD: activePalette (reference to GameConfig.PALETTES.MAGENTA)
// ADD: soundEnabled: true
// ADD: selectedMenu: 0
// ADD: previousState: null

Input._handleKeyDown (current):

  • Handles W/S/ArrowUp/ArrowDown for paddle movement
  • Digit1/2/3 for mode and difficulty selection
  • KeyR restart in gameover state // EXTEND: Add ArrowUp/Down for menu navigation in title/settings/modeSelect/diffSelect // EXTEND: Enter/Space to confirm selection // EXTEND: Escape/Backspace to go back (GameState.previousState) // ADD: Audio.init() call on first keydown (for Plan 02 — add the call even though Audio // module doesn't exist yet; guard with: if (typeof Audio !== 'undefined' && !Audio.isInitialized) Audio.init())

GameLoop.main() render switch (current states):

  • 'modeSelect' → plain white text prompt (Press 1/2)
  • 'diffSelect' → plain white text prompt (Press 1/2/3)
  • 'playing'/'scored'/'gameover' → gameplay with white colors // ADD: 'title' render case — arcade title screen // ADD: 'settings' render case — settings screen // RESTYLE: 'modeSelect' → neon arrow-key navigation // RESTYLE: 'diffSelect' → neon arrow-key navigation // RESTYLE: 'gameover' overlay — "PLAYER 1 WINS 7 4" format + Play Again / Main Menu // UPDATE: all hardcoded '#fff' colors → GameState.activePalette.accent
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add palette system to GameConfig and extend GameState</name>
  <files>index.html</files>
  <action>
In GameConfig, add after the AI_HARD entry:

```javascript
PALETTES: {
  MAGENTA: { accent: '#ff00cc', glow: 'rgba(255,0,204,0.6)', name: 'Magenta' },
  CYAN:    { accent: '#00ffff', glow: 'rgba(0,255,255,0.6)', name: 'Cyan' },
  LIME:    { accent: '#00ff00', glow: 'rgba(0,255,0,0.6)',   name: 'Lime' },
  WHITE:   { accent: '#ffffff', glow: 'rgba(255,255,255,0.6)', name: 'White' }
},
PALETTE_KEYS: ['MAGENTA', 'CYAN', 'LIME', 'WHITE']

In GameState, change gameState: 'modeSelect' to gameState: 'title' and add:

activePalette: null,   // Set after GameConfig defined — see init sequence
soundEnabled: true,
selectedMenu: 0,
previousState: null

After the GameState object definition (before Physics), add:

GameState.activePalette = GameConfig.PALETTES.MAGENTA;
// Also update paddle/ball color references to use palette at draw time (not stored on objects)

Do NOT store palette color on paddle1.color, paddle2.color, or ball.color — those properties stay as fallback '#fff'. The palette is applied at draw-call time by reading GameState.activePalette.accent in the renderer. This keeps the physics/AI logic untouched. Open index.html in a browser. The game starts at a blank black screen (title state). Open the browser console and run: console.log(GameState.activePalette.accent) — should log '#ff00cc'. console.log(GameConfig.PALETTE_KEYS) — should log ['MAGENTA','CYAN','LIME','WHITE']. GameConfig has PALETTES and PALETTE_KEYS. GameState has activePalette pointing to MAGENTA preset, soundEnabled:true, selectedMenu:0, previousState:null. gameState starts at 'title'.

Task 2: Rewrite Input keydown, add all render cases with neon styling index.html **Input._handleKeyDown rewrite:**

Replace the current keydown handler body with:

  1. Audio init guard (for Plan 02 integration — harmless no-op now):

    if (typeof Audio !== 'undefined' && !Audio.isInitialized) Audio.init();
    
  2. Continuous-hold keys (keep as-is):

    if (e.code === 'KeyW')      { this.keys.w = true; e.preventDefault(); }
    // ... etc
    
  3. Title screen navigation (gameState === 'title'):

    • ArrowUp → selectedMenu = Math.max(0, selectedMenu - 1)
    • ArrowDown → selectedMenu = Math.min(1, selectedMenu + 1) (two items: Play=0, Settings=1)
    • Enter OR Space → if selectedMenu===0: previousState='title', gameState='modeSelect', selectedMenu=0 if selectedMenu===1: previousState='title', gameState='settings', selectedMenu=0
  4. Mode select navigation (gameState === 'modeSelect'):

    • ArrowUp/Down → selectedMenu cycles between 0 (Solo vs AI) and 1 (2-Player)
    • Enter OR Space → if selectedMenu===0: mode='ai', previousState='modeSelect', gameState='diffSelect', selectedMenu=1 (default Medium) if selectedMenu===1: mode='2p', gameState='playing', Physics.serveBall()
    • Escape OR Backspace → gameState='title', selectedMenu=0
    • KEEP existing Digit1/Digit2 for backward compat (just in case)
  5. Difficulty select navigation (gameState === 'diffSelect'):

    • ArrowUp/Down → selectedMenu cycles 0 (Easy), 1 (Medium), 2 (Hard)
    • Enter OR Space → difficulty=['easy','medium','hard'][selectedMenu]; AI.init(); gameState='playing'; Physics.serveBall()
    • Escape OR Backspace → gameState='modeSelect', selectedMenu=0
    • KEEP existing Digit1/2/3 for backward compat
  6. Settings navigation (gameState === 'settings'): Items: 0=AI Difficulty, 1=Sound, 2=Color Scheme

    • ArrowUp/Down → selectedMenu cycles 0-2
    • ArrowLeft OR ArrowRight OR Enter OR Space → modify the selected setting:
      • If selectedMenu===0: cycle difficulty: easy→medium→hard→easy (ArrowLeft goes reverse)
      • If selectedMenu===1: toggle soundEnabled
      • If selectedMenu===2: cycle activePalette through PALETTE_KEYS array
        const idx = GameConfig.PALETTE_KEYS.indexOf(
          Object.keys(GameConfig.PALETTES).find(k => GameConfig.PALETTES[k] === GameState.activePalette)
        );
        const nextIdx = (idx + 1) % GameConfig.PALETTE_KEYS.length;
        GameState.activePalette = GameConfig.PALETTES[GameConfig.PALETTE_KEYS[nextIdx]];
        
        ArrowLeft goes (idx - 1 + length) % length
    • Escape OR Backspace → gameState = previousState (which is 'title'), selectedMenu=0
  7. Game over navigation (gameState === 'gameover'): Items: 0=Play Again, 1=Main Menu

    • ArrowUp/Down → selectedMenu cycles 0-1
    • Enter OR Space →
      • if selectedMenu===0 (Play Again): reset scores, winner, mode, difficulty; gameState='modeSelect'; selectedMenu=0; AI.init(); Physics.init(...)
      • if selectedMenu===1 (Main Menu): reset scores, winner, mode, difficulty; gameState='title'; selectedMenu=0; AI.init(); Physics.init(...)
    • KEEP KeyR restart (goes to modeSelect for backward compat)

GameLoop.main() render overhaul:

Helper function (add before GameLoop object): function drawNeonText(ctx, text, x, y, font, palette, blurRadius) { ctx.save(); ctx.shadowColor = palette.glow; ctx.shadowBlur = blurRadius || 15; ctx.fillStyle = palette.accent; ctx.font = font; ctx.fillText(text, x, y); ctx.restore(); }

Replace ALL occurrences of hardcoded '#fff' and 'rgba(255,255,255,0.8)' in render cases with GameState.activePalette.accent where appropriate. Score display, divider line (keep dim white), paddles, ball — all use palette.

Render cases to add/replace:

'title' case (add before modeSelect):

  • Renderer.clear()
  • Large glowing title: "SUPER PONG NEXT GEN" — bold 52px monospace, centered at logicalHeight/2 - 80, shadowBlur=20
  • Subtitle: "ARCADE EDITION" — 18px, shadowBlur=8, half-alpha glow, centered at logicalHeight/2 - 40
  • Menu items (Play at index 0, Settings at index 1) centered at logicalHeight/2 + 20 and +70
    • Selected item: bold 24px + shadowBlur=12 + leading > marker
    • Unselected: 20px + shadowBlur=0 + leading spaces
  • Footer hint: "ARROW KEYS to navigate, ENTER to select" — dim white, 14px, bottom of screen
  • early return

'settings' case (add after title):

  • Renderer.clear()
  • "SETTINGS" title — bold 36px, centered at y=80, shadowBlur=15
  • Items at y=170, 240, 310 (3 items with 70px spacing):
    • AI Difficulty: display current GameState.difficulty.toUpperCase() on right
    • Sound: display GameState.soundEnabled ? 'ON' : 'OFF' on right
    • Color Scheme: display GameState.activePalette.name on right
    • Selected item highlighted with shadowBlur=12, bold font, > marker
    • Unselected: normal weight, no glow
  • Footer: "ARROW KEYS to select, LEFT/RIGHT or ENTER to change, ESC to return" — dim white, 14px
  • early return

'modeSelect' case (restyle existing):

  • Renderer.clear()
  • "SELECT MODE" neon title — bold 36px, shadowBlur=15, centered at logicalHeight/2 - 90
  • Two items: "Solo vs AI" (index 0) and "2-Player Local" (index 1) — same highlight pattern as title menu
  • Footer hint: "ARROW KEYS + ENTER to select, ESC to go back"
  • early return (remove old Digit1/2 instructions from display, but keep handler)

'diffSelect' case (restyle existing):

  • Renderer.clear()
  • "SELECT DIFFICULTY" neon title
  • Three items: Easy / Medium / Hard — selectedMenu starts at 1 (Medium) when entering from modeSelect
  • Footer hint
  • early return

'gameover' overlay (restyle existing):

  • Build winner label: gs.winner === 'player1' ? 'PLAYER 1' : gs.winner === 'ai' ? 'AI' : 'PLAYER 2'
  • Score line: winnerLabel + ' WINS ' + gs.score1 + ' ' + gs.score2
  • Large neon text centered, shadowBlur=20
  • Two menu items below: "Play Again" (index 0), "Main Menu" (index 1) — with selection highlight
  • Remove old "Press R to play again" text

Gameplay rendering (playing/scored):

  • Change all p1.color, p2.color, b.color in drawRect/drawCircle calls to GameState.activePalette.accent
  • Score display: change 'rgba(255,255,255,0.8)' to GameState.activePalette.accent
  • Center divider: keep dim rgba(255,255,255,0.2) (neutral, not palette-colored) Open index.html. Verify: (1) Title screen renders with neon magenta glow on black background, menu shows Play and Settings. (2) Arrow keys change which item is highlighted. (3) Enter on Play goes to mode select with neon style. (4) Enter on Settings goes to settings screen showing difficulty/sound/palette. (5) Changing Color Scheme updates the palette (ball/paddle/text all change on next gameplay screen). (6) Completing a game shows "PLAYER X WINS N M" format with Play Again and Main Menu options. (7) Escape from settings/modeSelect returns to previous screen correctly. All 5 game states (title, settings, modeSelect, diffSelect, gameover) render with neon palette. Arrow-key navigation works. Palette switching affects all game colors. Game over shows winner + full score in the specified format.
Full manual playthrough checklist: 1. Fresh page load → title screen with neon magenta glow (SCRN-01) 2. Arrow keys highlight menu items; Enter on Play → mode select (SCRN-02) 3. Enter on Settings → settings screen; change palette to Cyan → start game → verify ball/paddles/scores are cyan (SCRN-03) 4. Complete a match → game over shows "PLAYER X WINS N M" with Play Again + Main Menu (SCRN-04) 5. Play Again returns to mode select with fresh scores; Main Menu returns to title 6. Escape from modeSelect returns to title; Escape from diffSelect returns to modeSelect

<success_criteria>

  • Title screen renders with neon branding on first load
  • All screens use consistent neon palette from GameState.activePalette
  • Arrow-key + Enter navigation works across all menu screens
  • Settings palette change propagates to ball/paddle/UI colors in gameplay
  • Game over displays "PLAYER X WINS N M" with functional navigation options
  • No hardcoded '#fff' in the actively-rendered gameplay/menu paths (all reference activePalette.accent) </success_criteria>
After completion, create `.planning/phases/03-complete-experience/03-01-SUMMARY.md`