Commit Graph
12 Commits
Author SHA1 Message Date
pastilhas bc8eeaf400 feat: implement digit entry in edit mode (Phase 40.3.3)
- Add setCell method to useBoard to update board state
- Handle digit keys 1-9 in edit mode in useCursor
- Move boardRendered counter to useBoard for proper cursor re-rendering
- Fix golden glow disappearing after digit entry
- Digits display with .user-entered class via renderer
2025-10-22 19:49:12 +01:00
pastilhas 0ce1d7fe65 feat: implement edit mode state machine (i and Escape)
Added mode state machine with visual feedback:

Mode State:
- CursorMode type: 'normal' | 'edit'
- Tracked in useCursor with useState
- Starts in 'normal' mode

Key Bindings:
- Press 'i' in normal mode → Enter edit mode
- Press 'Escape' in edit mode → Return to normal mode
- All vim motions (hjkl, anchors, etc.) only work in normal mode
- Edit mode blocks all movement keys

Visual Feedback:
- Normal mode: Blue outline (rgb(59, 130, 246))
- Edit mode: Green pulsing outline (rgb(34, 197, 94))
- Smooth pulse animation (1.5s ease-in-out infinite)
- cursor-edit CSS class applied in edit mode

Flow:
1. Game starts in normal mode (blue cursor)
2. Press 'i' → Green pulsing cursor (edit mode)
3. Press 'Escape' → Blue cursor (normal mode)

No digit entry yet - just state machine and visual indicators.
Phase 40.3.1 complete.
2025-10-22 19:22:15 +01:00
pastilhas 345b273669 feat: add red flash on invalid movement (Issue 3)
When user attempts to move out of bounds, show visual feedback:

CSS:
- Added .invalid-move class with red outline and background
- Animation: invalid-flash (500ms ease-out)
- Starts with bright red glow, fades out
- Red outline (3px solid) matches cursor style

Logic:
- flashInvalidMove() function in useCursor
- Triggers when move would go out of bounds (< 0 or > 8)
- Adds invalid-move class to current cell
- Removes class after 500ms
- Passes containerRef to useCursor for DOM access

Cleanup:
- Removed all console.log debugging statements
- Clean code ready for production

UX Flow:
- Press h at left edge → Red flash at current position
- Press j at bottom edge → Red flash at current position
- Press k at top edge → Red flash at current position
- Press l at right edge → Red flash at current position

Issue 3 complete!
2025-10-22 19:03:05 +01:00
pastilhas b8ff946114 fix: cursor now shows immediately on game start
Added boardRendered counter to track when board DOM is rebuilt:
- Increments after each renderBoard() call
- useCursorRender depends on boardRendered
- Forces cursor re-application after board rebuilds DOM

Flow:
1. Game starts
2. renderBoard() rebuilds DOM
3. boardRendered increments
4. useCursorRender triggers
5. Cursor class applied to (0,0)

This ensures cursor is visible immediately when pressing Space.
2025-10-22 18:57:57 +01:00
pastilhas 6d12a01f21 fix: cursor not showing on game start
Issue: When game starts, renderBoard() rebuilds DOM which removes cursor class
Solution: Add gameStatus as dependency to useCursorRender
- Now re-applies cursor after board renders
- Only applies cursor when gameStatus === 'started'

This ensures cursor is visible immediately when game starts at position (0,0).
2025-10-22 18:56:44 +01:00
pastilhas be41ccee14 feat: implement Phase 40.1 - basic cursor with hjkl movement
Add cursor navigation with hjkl keys (vim-style):

New Module Structure:
- src/useCursor/types.ts: Coord and CursorManager types
- src/useCursor/useCursor.ts: Cursor movement logic
- src/useCursor/useCursorRender.ts: Visual cursor highlight
- src/useCursor/index.ts: Public exports

Cursor Implementation:
- Position tracking with useRef (row/col from 0-8)
- Basic movement: moveLeft, moveRight, moveUp, moveDown
- Bounds checking (prevents moving outside 9x9 grid)
- Keyboard listener for h/j/k/l keys
- Only active when gameStatus === 'started'

Cursor Rendering:
- useCursorRender hook tracks cursor position
- Adds 'cursor' CSS class to current cell
- Removes from previous cell on movement
- Blue highlight with border (rgba(59, 130, 246, 0.3))

Integration:
- src/useGame.ts: Integrate useCursor and useCursorRender
- src/types.ts: Export CursorManager and Coord types
- src/useBoard/types.ts: NEW - BoardManager type
- src/game.css: Add cursor highlight styles

Game Flow:
- Press Space → Game starts → Cursor appears at (0,0)
- Press h/j/k/l → Cursor moves (bounded to grid)
- Visual feedback with blue highlight
- Press Q → Return to waiting state

Phase 40.1 complete. Next: Add number prefixes (5j) and other vim motions.
2025-10-22 18:49:24 +01:00
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
pastilhas 6f4a1accc4 feat: add difficulty selector to example app
Add interactive difficulty selection when game is idle:

Example App Changes:
- example/src/App.tsx: Add difficulty state and radio group
  - Import useState and Difficulty type
  - Radio buttons for Easy/Medium/Hard
  - Only visible when gameStatus === 'waiting'
  - Updates difficulty state on selection
  - Passes selected difficulty to useGame

Core Changes:
- src/useGame.ts: Respect difficulty changes
  - Add difficulty to useEffect dependencies
  - Regenerates board when difficulty changes

UX Flow:
1. User sees difficulty selector on idle screen
2. Selects difficulty (easy/medium/hard)
3. Presses Space to start
4. Puzzle generated with selected difficulty
5. On quit, returns to selector with last selection

Cell Counts:
- Easy: 40 empty cells (~41 givens)
- Medium: 50 empty cells (~31 givens)
- Hard: 60 empty cells (~21 givens)
2025-10-22 18:42:35 +01:00
pastilhas a6c4c5302f feat: implement Phase 20 - puzzle generation and rendering
Port sudoku generation algorithm and integrate with game flow:

Core Changes:
- src/useBoard/SudokuGenerator.ts: Ported from src_old/lib/Sudoku.ts
  - Full puzzle generation algorithm (robatron/sudoku.js)
  - Supports all difficulty levels (easy through inhuman)
  - Returns 81-character string representation

- src/useBoard/useBoard.ts: Enhanced with puzzle state
  - Added boardRef and initialBoardRef for state management
  - Added generateBoard(difficulty) method
  - Updated renderBoard() to display givens from board string
  - Cells show digits for givens, empty for '.' characters

- src/useGame.ts: Integrated game flow
  - Reads difficulty from GameOptions (defaults to 'easy')
  - Generates puzzle on startGame (gameStatus → 'started')
  - Re-renders board on status changes
  - quitGame returns to empty grid

Game Flow:
- Waiting: Empty 9x9 grid
- Press Space → startGame() → puzzle generated (~62 givens on easy)
- Press Q → quitGame() → empty grid

Testing platformHook integration:
- startGame trigger working via useKeyBindings
- quitGame trigger working via useKeyBindings
- Board state properly cleared/populated on status changes

Phase 20 complete. Next: Phase 30 (useGameStatus hook).
2025-10-22 18:24:08 +01:00
pastilhas b7d740386e feat: implement Phase 10 - minimal skeleton with empty grid
Create clean src/ structure with minimal viable implementation:

Core Files:
- src/types.ts: Basic type definitions (Difficulty, GameStatus, GameManager)
- src/index.ts: Main package exports
- src/game.css: Copied from src_old for styling
- src/useGame.ts: Orchestrator with mocked GameManager API
- src/useBoard/useBoard.ts: Renders empty 9x9 sudoku grid

Implementation Details:
- useBoard renders 81 cells with sudoku-grid/sudoku-cell classes
- Each cell has data-r and data-c attributes for identification
- useGame provides full GameManager interface to satisfy example app
- Real implementations: gameStatus, keyLog tracking, game controls
- Mocked implementations: cursor ({}), scoreManager (zeros/null)
- Supports optional platformHook parameter

Build Status:
- TypeScript compiles cleanly with no errors
- Example app builds successfully
- Ready for browser testing with empty grid display

Phase 10 complete. Next: Phase 20 (full useBoard with puzzle generation).
2025-10-22 18:18:06 +01:00
pastilhas 9292b83dd0 feat: prepare vim-sudoku for unified API implementation
- Archive previous implementation attempt (src -> src_failed)
- Create comprehensive IMPLEMENTATION_PLAN.md with phased approach
- Create IMPLEMENTATION.md to track implementation progress
- Update example app to match vim-maze patterns:
  - Simplify UI to desktop-only (no mobile support)
  - Add score metrics display (time, keystrokes, final score)
  - Update instructions for sudoku-specific keys (hjkl, i for edit)
  - Remove unnecessary mobile hooks

Phase 5 complete. Ready to begin Phase 10 (Setup & Core Types).
2025-10-22 18:08:33 +01:00
pastilhas d0a51d7079 starting new structure 2025-10-19 17:59:12 +01:00