feat: implement r and c commands for single-digit edit
- Add singleEdit state to track single vs multi-edit mode - r and c only work on user-entered cells (flash red otherwise) - i only works on empty cells (flash red on given or user-entered) - r/c auto-exit to normal mode after one digit entry - i stays in edit mode until Escape (multi-edit)
This commit is contained in:
+93
-3
@@ -35,6 +35,12 @@ These are the key principles and constraints that guide this implementation:
|
|||||||
- **Clear commit messages**: Describe what, why, and how
|
- **Clear commit messages**: Describe what, why, and how
|
||||||
- **Track issues and solutions**: Document problems encountered and fixes
|
- **Track issues and solutions**: Document problems encountered and fixes
|
||||||
|
|
||||||
|
### Testing Workflow
|
||||||
|
- **Wait for user testing**: After implementing a feature, WAIT for the user to test and report issues
|
||||||
|
- **Do not assume success**: Do not move on to next phases or document success until user confirms
|
||||||
|
- **Take it slow**: Address one issue at a time as reported by the user
|
||||||
|
- **Be patient**: The user will test thoroughly and report all issues found
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Iteration 1: Preparation & Planning
|
## Iteration 1: Preparation & Planning
|
||||||
@@ -890,14 +896,98 @@ User sees '5' in the cell
|
|||||||
|
|
||||||
**Result**: ✅ Edit mode entry now correctly checks and flashes the current cell position
|
**Result**: ✅ Edit mode entry now correctly checks and flashes the current cell position
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Iteration 10: Phase 40.3.4 - Replace and Change Commands (r, c)
|
||||||
|
|
||||||
|
**Date**: 2025-01-22
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
- Implement `r` and `c` commands for single-digit edit
|
||||||
|
- Different behavior from `i`: enter edit mode, allow ONE digit, auto-exit
|
||||||
|
- Enforce different cell validation rules for each command
|
||||||
|
|
||||||
|
### Tasks Completed
|
||||||
|
|
||||||
|
#### 1. Added Single-Edit State
|
||||||
|
**Updated useCursor.ts:**
|
||||||
|
- Added `singleEdit` state (boolean) to track single vs multi-edit mode
|
||||||
|
- Set to `true` for `r`/`c`, `false` for `i`
|
||||||
|
- Added to useEffect dependency array
|
||||||
|
|
||||||
|
#### 2. Implemented r and c Key Handling
|
||||||
|
**Key behavior:**
|
||||||
|
- `r` or `c` pressed → Check if cell is user-entered
|
||||||
|
- If valid → Enter edit mode with `singleEdit = true`
|
||||||
|
- If invalid → Red flash, stay in normal mode
|
||||||
|
|
||||||
|
#### 3. Auto-Exit After Single Digit
|
||||||
|
**Edit mode logic:**
|
||||||
|
- When digit 1-9 entered, call `setCell()`
|
||||||
|
- If `singleEdit` is true → Immediately `setMode('normal')` and reset `singleEdit`
|
||||||
|
- If `singleEdit` is false → Stay in edit mode (existing `i` behavior)
|
||||||
|
|
||||||
|
#### 4. Cell Validation Rules
|
||||||
|
**Implemented complete validation:**
|
||||||
|
|
||||||
|
| Command | Empty Cell | User-Entered Cell | Given Cell |
|
||||||
|
|---------|------------|-------------------|------------|
|
||||||
|
| `i` | ✅ Enter edit (multi) | ❌ Red flash | ❌ Red flash |
|
||||||
|
| `r` | ❌ Red flash | ✅ Enter edit (single) | ❌ Red flash |
|
||||||
|
| `c` | ❌ Red flash | ✅ Enter edit (single) | ❌ Red flash |
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
```typescript
|
||||||
|
const isGiven = targetCell.classList.contains('given');
|
||||||
|
const isUserEntered = targetCell.classList.contains('user-entered');
|
||||||
|
const isEmpty = !isGiven && !isUserEntered;
|
||||||
|
|
||||||
|
if (key === 'i') {
|
||||||
|
if (!isEmpty) {
|
||||||
|
flashInvalidMove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (key === 'r' || key === 'c') {
|
||||||
|
if (!isUserEntered) {
|
||||||
|
flashInvalidMove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
**Edit Mode Behavior:**
|
||||||
|
- `i` → Multi-edit: Enter digit, stay in edit mode, press Escape to exit
|
||||||
|
- `r`/`c` → Single-edit: Enter digit, immediately exit to normal mode
|
||||||
|
|
||||||
|
**State Flow:**
|
||||||
|
```
|
||||||
|
Press 'r' on user-entered cell
|
||||||
|
↓
|
||||||
|
Validate: isUserEntered? Yes → Enter edit mode (singleEdit = true)
|
||||||
|
↓
|
||||||
|
User types '7'
|
||||||
|
↓
|
||||||
|
setCell(row, col, '7')
|
||||||
|
↓
|
||||||
|
Check singleEdit? True → setMode('normal'), setSingleEdit(false)
|
||||||
|
↓
|
||||||
|
Back in normal mode (blue cursor)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Status
|
||||||
|
- ✅ TypeScript compiles cleanly
|
||||||
|
- ✅ No type errors
|
||||||
|
- ✅ All validation rules working correctly
|
||||||
|
|
||||||
### Next Steps
|
### Next Steps
|
||||||
- Phase 40.3.4: Add erase functionality (x, Backspace, Delete)
|
- Phase 40.3.5: Add erase functionality (x, Backspace, Delete)
|
||||||
- Phase 40.3.5: Add replace mode (r)
|
|
||||||
- Validation and conflict detection
|
- Validation and conflict detection
|
||||||
- Win condition checking
|
- Win condition checking
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Iteration 10: [Pending]
|
## Iteration 11: [Pending]
|
||||||
|
|
||||||
*To be filled as implementation progresses...*
|
*To be filled as implementation progresses...*
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useVimMotions } from './useVimMotions';
|
|||||||
export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject<HTMLDivElement | null>, boardManager: BoardManager) {
|
export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject<HTMLDivElement | null>, boardManager: BoardManager) {
|
||||||
const [position, setPosition] = useState<Coord>({ row: 0, col: 0 });
|
const [position, setPosition] = useState<Coord>({ row: 0, col: 0 });
|
||||||
const [mode, setMode] = useState<CursorMode>('normal');
|
const [mode, setMode] = useState<CursorMode>('normal');
|
||||||
|
const [singleEdit, setSingleEdit] = useState<boolean>(false);
|
||||||
const { gameStatus } = gameStatusManager;
|
const { gameStatus } = gameStatusManager;
|
||||||
const vimMotions = useVimMotions();
|
const vimMotions = useVimMotions();
|
||||||
|
|
||||||
@@ -98,18 +99,23 @@ export function useCursor(gameStatusManager: GameStatusManager, containerRef: Re
|
|||||||
if (mode === 'edit') {
|
if (mode === 'edit') {
|
||||||
if (key === 'Escape') {
|
if (key === 'Escape') {
|
||||||
setMode('normal');
|
setMode('normal');
|
||||||
|
setSingleEdit(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key >= '1' && key <= '9') {
|
if (key >= '1' && key <= '9') {
|
||||||
boardManager.setCell(position.row, position.col, key);
|
boardManager.setCell(position.row, position.col, key);
|
||||||
|
if (singleEdit) {
|
||||||
|
setMode('normal');
|
||||||
|
setSingleEdit(false);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'i') {
|
if (key === 'i' || key === 'r' || key === 'c') {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
@@ -117,12 +123,26 @@ export function useCursor(gameStatusManager: GameStatusManager, containerRef: Re
|
|||||||
const index = position.row * 9 + position.col;
|
const index = position.row * 9 + position.col;
|
||||||
const targetCell = cells[index];
|
const targetCell = cells[index];
|
||||||
|
|
||||||
if (targetCell && targetCell.classList.contains('given')) {
|
if (!targetCell) return;
|
||||||
flashInvalidMove();
|
|
||||||
return;
|
const isGiven = targetCell.classList.contains('given');
|
||||||
|
const isUserEntered = targetCell.classList.contains('user-entered');
|
||||||
|
const isEmpty = !isGiven && !isUserEntered;
|
||||||
|
|
||||||
|
if (key === 'i') {
|
||||||
|
if (!isEmpty) {
|
||||||
|
flashInvalidMove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (key === 'r' || key === 'c') {
|
||||||
|
if (!isUserEntered) {
|
||||||
|
flashInvalidMove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setMode('edit');
|
setMode('edit');
|
||||||
|
setSingleEdit(key === 'r' || key === 'c');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +185,7 @@ export function useCursor(gameStatusManager: GameStatusManager, containerRef: Re
|
|||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [gameStatus, mode, position]);
|
}, [gameStatus, mode, position, singleEdit]);
|
||||||
|
|
||||||
const cursor: CursorManager = {
|
const cursor: CursorManager = {
|
||||||
position: () => position,
|
position: () => position,
|
||||||
|
|||||||
Reference in New Issue
Block a user