Files
vim-sudoku/IMPLEMENTATION.md
T
pastilhas 4e14d54122 feat: implement Phase 30 - useGameStatus hook
Extract game status logic into dedicated hook following vim-maze pattern:

New Module Structure:
- src/useGameStatus/types.ts: GameStatus and GameStatusManager types
- src/useGameStatus/useGameStatus.ts: Hook implementation
- src/useGameStatus/index.ts: Public exports

Hook Implementation:
- State machine with 5 states (waiting/started/paused/game-over/game-won)
- Methods: startGame, togglePause, quitGame, setGameStatus
- Clean state transitions with useState
- Simpler than vim-maze (no intermediate 'has-key' state)

Updated Core Files:
- src/useGame.ts: Use useGameStatus() instead of inline useState
  - Compose gameStatusManager into GameManager
  - Cleaner separation of concerns

- src/types.ts: Import and re-export GameStatus types
  - GameManager extends GameStatusManager (intersection type)
  - Single source of truth for status types

- src/index.ts: Export useGameStatus hook publicly

Benefits:
- Reusable state machine
- Easier to test in isolation
- Follows unified API architecture
- Matches vim-maze patterns

State Transitions:
waiting → startGame() → started ↔ togglePause() ↔ paused
started → (win/lose) → game-won/game-over
* → quitGame() → waiting

Phase 30 complete. Next: Phase 40 (useCursor composition).
2025-10-22 18:45:56 +01:00

13 KiB

vim-sudoku Unified API Implementation Log

This document tracks the step-by-step implementation of the unified API architecture for vim-sudoku.

Overview

We are porting vim-sudoku from the old architecture (src_old) to follow the unified API patterns established by vim-maze. This enables consistent architecture across all VIMazing games and better platform integration.


Iteration 1: Preparation & Planning

Date: 2025-01-22

Goals

  • Review existing implementations (vim-maze, src_old, src_failed)
  • Create implementation plan
  • Prepare example app for new API

Tasks Completed

1. Reviewed Architecture

  • Analyzed vim-maze unified API implementation
  • Examined src_old working implementation
  • Identified src_failed attempt issues
  • Documented key differences between vim-maze and vim-sudoku

2. Created Implementation Plan

  • Created IMPLEMENTATION_PLAN.md with 7 phases (numbered x10 for flexibility)
  • Defined file structure matching vim-maze patterns
  • Documented key design decisions
  • Established type mappings between vim-maze and vim-sudoku
  • Listed success criteria

3. Prepared Example App (Phase 5)

  • Updated example/src/App.tsx to match vim-maze structure
  • Simplified to desktop-only (no mobile support)
  • Added score metrics display (time, keystrokes, final score)
  • Updated game status messages for sudoku context
  • Changed navigation instructions (hjkl + i for edit mode)
  • Verified useKeyBindings already matches vim-maze

File Changes

vim-sudoku/
├── IMPLEMENTATION_PLAN.md       [CREATED] - Detailed implementation roadmap
├── IMPLEMENTATION.md             [CREATED] - This file, tracking progress
├── example/src/App.tsx           [MODIFIED] - Simplified, sudoku-focused UI
└── src/ → src_failed/            [RENAMED] - Previous failed attempt archived

Key Decisions

  1. No Mobile Support: Sudoku with vim bindings requires keyboard, mobile not practical
  2. Simple Metrics: Start with time, keystrokes, and final score (no efficiency yet)
  3. Phase Numbering: Use x10 numbering (10, 20, 30...) to allow insertions

Next Steps

  • Phase 10: Setup & Core Types
    • Create clean src/ directory structure
    • Define all type definitions
    • Set up module exports

Iteration 2: Phase 10 - Minimal Skeleton

Date: 2025-01-22

Goals

  • Create clean src/ directory structure
  • Implement minimal types
  • Build empty 9x9 grid rendering
  • Mock GameManager API for example app
  • Get visual feedback without errors

Tasks Completed

1. Created Core Structure

src/
├── game.css              [COPIED] - From src_old
├── index.ts              [CREATED] - Main exports
├── types.ts              [CREATED] - Basic type definitions
├── useGame.ts            [CREATED] - Orchestrator with mocks
└── useBoard/
    └── useBoard.ts       [CREATED] - Empty grid rendering

2. Implemented Types (src/types.ts)

  • Difficulty - Puzzle difficulty levels
  • GameStatus - Game state machine states
  • GameOptions - useGame configuration
  • GameManager - Full API interface (mocked for now)
  • KeyLogEntry - Keystroke tracking

3. Implemented useBoard (src/useBoard/useBoard.ts)

  • containerRef - DOM reference
  • renderBoard() - Renders empty 9x9 sudoku grid
  • Creates proper HTML structure with sudoku-grid and sudoku-cell classes
  • Sets data attributes (data-r, data-c) for cell identification

4. Implemented useGame (src/useGame.ts)

  • Calls useBoard() for grid rendering
  • Returns full GameManager interface
  • Real implementations:
    • gameStatus state management
    • startGame, togglePause, quitGame
    • keyLog tracking with clearKeyLog, getKeyLog
  • Mocked implementations:
    • cursor (empty object)
    • scoreManager (zeros and null)
  • Supports optional platformHook parameter

5. Verified Build

  • ✅ TypeScript compilation successful
  • ✅ Example app builds without errors
  • ✅ No runtime errors expected

Technical Details

Empty Grid Rendering:

  • 81 cells (9x9) created with sudoku-cell class
  • Each cell has data-r and data-c attributes
  • Wrapped in sudoku-grid div inside #sudoku container
  • Uses CSS from src_old for styling

Mocking Strategy:

  • Return full GameManager interface to satisfy example app
  • Mock methods do nothing (empty functions)
  • Mock values return defaults (0, null, empty arrays)
  • Real state management only for critical path (gameStatus)

Next Steps

  • Phase 20: Implement full useBoard with SudokuGenerator
    • Port puzzle generation logic
    • Add board state management
    • Implement cell validation

Iteration 3: Phase 20 - Puzzle Generation & Rendering

Date: 2025-01-22

Goals

  • Port SudokuGenerator from src_old
  • Generate valid sudoku puzzles
  • Render given numbers in correct positions
  • Test startGame/quitGame integration via platformHook

Tasks Completed

1. Ported SudokuGenerator

  • Copied src_old/lib/Sudoku.ts → src/useBoard/SudokuGenerator.ts
  • Full puzzle generation algorithm from robatron/sudoku.js
  • Supports all difficulty levels (easy, medium, hard, very-hard, insane, inhuman)
  • Returns 81-character string representation

2. Enhanced useBoard

  • Added boardRef - Current board state (81-char string)
  • Added initialBoardRef - Initial puzzle state for reset
  • Added generateBoard(difficulty) - Creates new puzzle
  • Updated renderBoard() - Displays numbers from board state
  • Cells show given digits, empty for '.' characters

3. Updated useGame

  • Reads difficulty from GameOptions (defaults to 'easy')
  • Calls generateBoard() when gameStatus becomes 'started'
  • Re-renders board on game status changes
  • startGame → generates puzzle and displays it
  • quitGame → clears to empty board

4. Game Flow Integration

  • Waiting state: Empty 9x9 grid displayed
  • Press Space: Triggers startGame via platformHook
  • Started state: New puzzle generated with ~62 givens (easy mode)
  • Press Q: Triggers quitGame, returns to empty grid

Technical Details

Board Representation:

boardRef.current = "52...8.4....7...4......6.....8.....3.9....8..1...2.......2....6...3....7..8...9."
// 81 characters: '1'-'9' for givens, '.' for empty cells

Rendering Logic:

  • Iterate through 81 characters
  • Create cell with data-r and data-c attributes
  • If character is digit → display in cell
  • If character is '.' → leave cell empty
  • Uses existing CSS classes from game.css

Game Status Flow:

waiting (empty grid) 
   ↓ [space pressed]
started (puzzle generated & rendered)
   ↓ [q pressed]
waiting (empty grid)

Testing Notes

  • ✅ TypeScript compiles cleanly
  • ✅ Example app builds successfully
  • ✅ Easy difficulty generates ~62 given numbers
  • ✅ platformHook integration working (startGame/quitGame)
  • Ready for browser testing with actual puzzle display

Next Steps

  • Phase 30: Implement useGameStatus hook
    • Proper state machine with transitions
    • Handle paused state
    • Integrate with scoring

Iteration 4: Phase 21 - Better Generation Algorithm

Date: 2025-01-22

Goals

  • Replace Robatron's algorithm with pattern-based generation
  • Separate generator and renderer into clean classes
  • Always generate valid, solvable puzzles
  • Store both puzzle and solution references
  • Port unit tests for validation

Tasks Completed

1. Replaced SudokuGenerator

  • Old: Robatron's backtracking algorithm (372 lines, complex)
  • New: Pattern-based generation from src_old/sudoku/ (~130 lines, simple)
  • Algorithm:
    1. Generate fully solved board using base pattern + shuffles
    2. Validate complete board (sum checks for rows/cols/boxes)
    3. Remove X cells based on difficulty to create puzzle
    4. Keep solution reference (no solving needed!)

2. Implemented Class-Based Architecture

SudokuGenerator class:

  • generate(difficulty) - Creates puzzle + solution
  • getPuzzle() - Returns puzzle string (with '.' for empty)
  • getSolution() - Returns complete solution
  • validateFullBoard() - Private validation (defensive)
  • Configurable difficulty at top of file

SudokuRenderer class:

  • render(board, initialBoard) - Renders current state
  • renderEmpty() - Renders empty 9x9 grid
  • Distinguishes given cells from user-entered cells
  • Adds CSS classes: 'given', 'user-entered'

3. Updated useBoard

  • Stores refs: generatorRef, rendererRef
  • Stores state: boardRef, solutionRef, initialBoardRef
  • Uses class instances for generation and rendering
  • Clean separation of concerns

4. Difficulty Configuration

const CELLS_TO_REMOVE = {
  easy: 40,    // ~41 givens
  medium: 50,  // ~31 givens
  hard: 60,    // ~21 givens
} as const;

5. Ported Unit Tests

  • SudokuGenerator.test.ts using bun:test
  • Tests: valid board generation (100x)
  • Tests: correct cell removal counts
  • Tests: puzzle/solution consistency

Technical Details

Why This Algorithm is Better:

  1. Always valid: Starts with mathematically valid complete board
  2. Always solvable: We keep the solution, don't need to solve
  3. Fast: No backtracking, just pattern + shuffle
  4. Simple: Easy to understand and maintain
  5. Predictable difficulty: Just cell count, not algorithmic complexity

Pattern-Based Generation:

// Base pattern ensures valid structure
pattern(r, c) = (3 * (r % 3) + floor(r / 3) + c) % 9 + 1

// Then shuffle:
// - Rows within bands (0-2, 3-5, 6-8)
// - Cols within stacks (0-2, 3-5, 6-8)
// - Band order
// - Stack order
// - Digit mapping (1-9)

Rendering Enhancement:

  • Given cells: <div class="sudoku-cell given">5</div>
  • User-entered: <div class="sudoku-cell user-entered">7</div>
  • Empty: <div class="sudoku-cell"></div>

Build Status

  • ✅ TypeScript compiles cleanly
  • ✅ Example app builds successfully
  • ✅ Unit tests ready (bun test)
  • ✅ Algorithm validated with 100+ board generations

Next Steps

  • Phase 30: Implement useGameStatus hook
    • Proper state machine
    • Handle paused state
    • Win condition detection

Iteration 5: Phase 30 - useGameStatus Hook

Date: 2025-01-22

Goals

  • Extract game status logic into dedicated hook
  • Follow vim-maze architecture pattern
  • Clean separation of concerns
  • Reusable state machine

Tasks Completed

1. Created useGameStatus Module

File Structure:

src/useGameStatus/
├── index.ts              # Public exports
├── types.ts              # GameStatus, GameStatusManager
└── useGameStatus.ts      # Hook implementation

2. Implemented State Machine

States:

  • waiting - Initial state, awaiting start
  • started - Game in progress
  • paused - Game temporarily paused
  • game-over - Game lost
  • game-won - Game completed

State Transitions:

waiting ──startGame()──> started
               ▲            │
               │            │ togglePause()
               │            ▼
              quitGame()  paused
                           │
                           │ togglePause()
                           ▼
                        started

3. Hook API

const gameStatusManager = useGameStatus();
const {
  gameStatus,        // Current state
  setGameStatus,     // Direct setter (for special cases)
  startGame,         // waiting → started
  togglePause,       // started ↔ paused
  quitGame,          // * → waiting
} = gameStatusManager;

4. Updated useGame

  • Removed inline useState<GameStatus>
  • Uses useGameStatus() hook
  • Spreads gameStatusManager into GameManager
  • Cleaner, more maintainable code

5. Updated Type System

types.ts:

  • Imports GameStatus and GameStatusManager from useGameStatus
  • Re-exports for public API
  • GameManager extends GameStatusManager (intersection type)
  • Avoids duplication, single source of truth

Technical Details

Simpler than vim-maze:

  • No intermediate states (no 'has-key' equivalent)
  • Straightforward: waiting → started → won/over
  • Pause is optional toggle, not a separate flow

Hook Composition:

// In useGame
const gameStatusManager = useGameStatus();
const { gameStatus } = gameStatusManager;

// Compose into GameManager
const gameManager = {
  ...gameStatusManager,  // gameStatus, startGame, etc.
  // ... other managers
};

Type Safety:

  • GameManager extends GameStatusManager
  • All status methods available on gameManager
  • Full TypeScript inference throughout

Build Status

  • ✅ TypeScript compiles cleanly
  • ✅ Example app builds successfully
  • ✅ No breaking changes to public API

Next Steps

  • Phase 40: useCursor Hook Composition
    • useCellEditor (analog to useHero)
    • useVimMotions
    • useGameKeys
    • useCursorRender

Iteration 6: [Pending]

To be filled as implementation progresses...