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
This commit is contained in:
+124
-1
@@ -762,6 +762,129 @@ Red flash → Stay in Normal
|
||||
|
||||
---
|
||||
|
||||
## Iteration 9: [Pending]
|
||||
## Iteration 9: Phase 40.3.3 - Digit Entry
|
||||
|
||||
**Date**: 2025-01-22
|
||||
|
||||
### Goals
|
||||
- Allow user to type digits 1-9 when in edit mode
|
||||
- Update board state with entered digits
|
||||
- Cells show digits with `.user-entered` class
|
||||
- Stay in edit mode after entering digit
|
||||
|
||||
### Tasks Completed
|
||||
|
||||
#### 1. Added setCell Method to BoardManager
|
||||
**Updated useBoard.ts:**
|
||||
- Added `setCell(row, col, value)` function
|
||||
- Updates board string at calculated index
|
||||
- Triggers re-render to display change
|
||||
|
||||
**Implementation:**
|
||||
```typescript
|
||||
function setCell(row: number, col: number, value: string): void {
|
||||
const board = boardRef.current.split('');
|
||||
const index = row * 9 + col;
|
||||
board[index] = value;
|
||||
boardRef.current = board.join('');
|
||||
renderBoard();
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Updated BoardManager Type
|
||||
- Added `setCell: (row: number, col: number, value: string) => void;` to type definition
|
||||
|
||||
#### 3. Enhanced useCursor for Digit Entry
|
||||
**Updated useCursor.ts:**
|
||||
- Added `boardManager` parameter (BoardManager)
|
||||
- In edit mode, detect digit keys (1-9)
|
||||
- Call `boardManager.setCell()` to update board
|
||||
- Stay in edit mode (no auto-exit)
|
||||
|
||||
**Key Handling:**
|
||||
```typescript
|
||||
if (mode === 'edit') {
|
||||
if (key === 'Escape') {
|
||||
setMode('normal');
|
||||
return;
|
||||
}
|
||||
|
||||
if (key >= '1' && key <= '9') {
|
||||
boardManager.setCell(position.row, position.col, key);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Updated useGame Integration
|
||||
- Pass `board` (BoardManager) to `useCursor()`
|
||||
- Three-parameter call: `useCursor(gameStatusManager, containerRef, board)`
|
||||
|
||||
### Technical Details
|
||||
|
||||
**Board State Flow:**
|
||||
```
|
||||
User presses '5' in edit mode
|
||||
↓
|
||||
useCursor detects digit key
|
||||
↓
|
||||
Calls boardManager.setCell(row, col, '5')
|
||||
↓
|
||||
Updates boardRef.current string
|
||||
↓
|
||||
Calls renderBoard()
|
||||
↓
|
||||
SudokuRenderer.render() displays digit with .user-entered class
|
||||
↓
|
||||
User sees '5' in the cell
|
||||
```
|
||||
|
||||
**Cell Classification:**
|
||||
- Given cells: Have `.given` class (from initial puzzle)
|
||||
- User-entered cells: Have `.user-entered` class (from renderer logic)
|
||||
- Empty cells: No value displayed
|
||||
|
||||
**Mode Behavior:**
|
||||
- Edit mode entered with `i` on empty/user-entered cells
|
||||
- Typing 1-9 places digit, stays in edit mode
|
||||
- `Escape` exits to normal mode
|
||||
- Can navigate to other cells and repeat
|
||||
|
||||
### Build Status
|
||||
- ✅ TypeScript compiles cleanly
|
||||
- ✅ No type errors
|
||||
- ✅ Integration complete
|
||||
|
||||
### Issue 1: Golden Glow Disappearing After Digit Entry
|
||||
|
||||
**Problem**: When typing a digit in edit mode, the `.editing` class (golden glow) disappeared even though we remained in edit mode.
|
||||
|
||||
**Root Cause**: `setCell()` calls `renderBoard()` which rebuilds the entire DOM, wiping out the cursor highlight classes.
|
||||
|
||||
**Solution**: Moved `boardRendered` counter from `useGame` to `useBoard`
|
||||
- Added `boardRendered` state to `useBoard` hook
|
||||
- Increment counter at end of `renderBoard()` function
|
||||
- Export `boardRendered` from `useBoard`
|
||||
- `useCursorRender` depends on `boardRendered` and re-applies cursor classes after DOM rebuild
|
||||
|
||||
**Changes**:
|
||||
- `useBoard.ts`: Added `useState` for `boardRendered`, increment in `renderBoard()`
|
||||
- `useBoard/types.ts`: Added `boardRendered: number` to `BoardManager`
|
||||
- `useGame.ts`: Removed local `boardRendered` state, use board's `boardRendered` instead
|
||||
- Removed manual `setBoardRendered` calls from `useGame` (automatic now)
|
||||
|
||||
**Result**: ✅ Golden glow persists when typing digits in edit mode
|
||||
|
||||
### Next Steps
|
||||
- Phase 40.3.4: Add erase functionality (x, Backspace, Delete)
|
||||
- Phase 40.3.5: Add replace mode (r)
|
||||
- Validation and conflict detection
|
||||
- Win condition checking
|
||||
|
||||
---
|
||||
|
||||
## Iteration 10: [Pending]
|
||||
|
||||
*To be filled as implementation progresses...*
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { Difficulty } from '../types';
|
||||
|
||||
export type BoardManager = {
|
||||
containerRef: RefObject<HTMLDivElement | null>;
|
||||
boardRendered: number;
|
||||
generateBoard: (difficulty: Difficulty) => void;
|
||||
renderBoard: () => void;
|
||||
setCell: (row: number, col: number, value: string) => void;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { SudokuGenerator } from './SudokuGenerator';
|
||||
import { SudokuRenderer } from './SudokuRenderer';
|
||||
import type { Difficulty } from './SudokuGenerator';
|
||||
@@ -10,6 +10,7 @@ export function useBoard() {
|
||||
const boardRef = useRef<string>('');
|
||||
const solutionRef = useRef<string>('');
|
||||
const initialBoardRef = useRef<string>('');
|
||||
const [boardRendered, setBoardRendered] = useState(0);
|
||||
|
||||
function generateBoard(difficulty: Difficulty = 'easy') {
|
||||
if (!generatorRef.current) {
|
||||
@@ -41,6 +42,16 @@ export function useBoard() {
|
||||
} else {
|
||||
rendererRef.current.renderEmpty();
|
||||
}
|
||||
|
||||
setBoardRendered(prev => prev + 1);
|
||||
}
|
||||
|
||||
function setCell(row: number, col: number, value: string): void {
|
||||
const board = boardRef.current.split('');
|
||||
const index = row * 9 + col;
|
||||
board[index] = value;
|
||||
boardRef.current = board.join('');
|
||||
renderBoard();
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -50,7 +61,9 @@ export function useBoard() {
|
||||
boardRef,
|
||||
solutionRef,
|
||||
initialBoardRef,
|
||||
boardRendered,
|
||||
generateBoard,
|
||||
renderBoard,
|
||||
setCell,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import type { Coord, CursorManager, CursorMode } from './types';
|
||||
import type { GameStatusManager } from '../useGameStatus/types';
|
||||
import type { BoardManager } from '../useBoard/types';
|
||||
import { useVimMotions } from './useVimMotions';
|
||||
|
||||
export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject<HTMLDivElement | null>) {
|
||||
export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject<HTMLDivElement | null>, boardManager: BoardManager) {
|
||||
const [position, setPosition] = useState<Coord>({ row: 0, col: 0 });
|
||||
const [mode, setMode] = useState<CursorMode>('normal');
|
||||
const { gameStatus } = gameStatusManager;
|
||||
@@ -97,7 +98,14 @@ export function useCursor(gameStatusManager: GameStatusManager, containerRef: Re
|
||||
if (mode === 'edit') {
|
||||
if (key === 'Escape') {
|
||||
setMode('normal');
|
||||
return;
|
||||
}
|
||||
|
||||
if (key >= '1' && key <= '9') {
|
||||
boardManager.setCell(position.row, position.col, key);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+2
-6
@@ -8,15 +8,14 @@ import { useCursorRender } from './useCursor/useCursorRender';
|
||||
export function useGame(options: GameOptions = {}, platformHook?: unknown): GameManager {
|
||||
const { difficulty = 'easy' } = options;
|
||||
const board = useBoard();
|
||||
const { containerRef, renderBoard, generateBoard } = board;
|
||||
const { containerRef, renderBoard, generateBoard, boardRendered } = board;
|
||||
|
||||
const gameStatusManager = useGameStatus();
|
||||
const { gameStatus } = gameStatusManager;
|
||||
|
||||
const [keyLog, setKeyLog] = useState<{ key: string; timestamp: number }[]>([]);
|
||||
const [boardRendered, setBoardRendered] = useState(0);
|
||||
|
||||
const cursor = useCursor(gameStatusManager, containerRef);
|
||||
const cursor = useCursor(gameStatusManager, containerRef, board);
|
||||
const cursorPosition = cursor.position();
|
||||
const cursorMode = cursor.mode();
|
||||
|
||||
@@ -24,17 +23,14 @@ export function useGame(options: GameOptions = {}, platformHook?: unknown): Game
|
||||
|
||||
useEffect(() => {
|
||||
renderBoard();
|
||||
setBoardRendered(prev => prev + 1);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (gameStatus === 'started') {
|
||||
generateBoard(difficulty);
|
||||
renderBoard();
|
||||
setBoardRendered(prev => prev + 1);
|
||||
} else if (gameStatus === 'waiting') {
|
||||
renderBoard();
|
||||
setBoardRendered(prev => prev + 1);
|
||||
}
|
||||
}, [gameStatus, difficulty]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user