VIMazing VIM Sudoku engine โ a lightweight, typed React hook set for sudoku games.
npm install @vimazing/vim-sudoku


Lightweight, typed React hooks for building interactive sudoku games with VIM-style modal editing.
Part of the VIMazing project.
---
---
- ๐ฎ VIM modal editing โ Navigate in normal mode, edit cells with i/r/c commands
- โจ๏ธ Full VIM motions โ hjkl, counts (5j), anchors (^/$), gg/G, repeat (.)
- ๐ฏ Smart cell protection โ Pre-filled clues locked with visual feedback
- ๐ก Hint system โ Get hints for incorrect cells or empty cells (Shift+H)
- โฑ๏ธ Configurable limits โ Time limits and hint penalties trigger game-over
- ๐ Comprehensive scoring โ Time + keystrokes + hints with difficulty multipliers
- ๐จ Tokyo Night theme โ Beautiful dark theme with proper 3x3 box separators
- ๐ฆ Full TypeScript โ Complete type safety with generated declarations
- ๐ช Composable architecture โ Clean separation: board, cursor, score, game status
- ๐ Platform hooks โ Optional integration for analytics and custom bindings
---
``bash`
npm install @vimazing/vim-sudoku
Or with bun:
`bash`
bun add @vimazing/vim-sudoku
---
`tsx
import { useGame } from "@vimazing/vim-sudoku";
import "@vimazing/vim-sudoku/game.css";
export function SudokuGame() {
const gameManager = useGame({ difficulty: 'easy' });
const { containerRef, gameStatus, scoreManager, startGame } = gameManager;
return (
Score: {scoreManager.finalScore} / 1000
Time: {Math.floor(scoreManager.timeValue / 1000)}s
> Note: You must manually import
game.css for styling.---
API Reference
$3
Main orchestrator hook that composes all game functionality.
#### Options
`typescript
type GameOptions = {
difficulty?: 'easy' | 'medium' | 'hard'; // Default: 'easy'
timeLimit?: number; // In seconds, default: 600 (10 min)
removedCells?: number; // Override difficulty default
};
`Difficulty Defaults:
-
easy: 25 cells removed (~56 givens)
- medium: 40 cells removed (~41 givens)
- hard: 50 cells removed (~31 givens)Examples:
`typescript
// Default configuration
useGame()// Hard difficulty with defaults
useGame({ difficulty: 'hard' })
// Custom puzzle size
useGame({ removedCells: 30 })
// Custom everything
useGame({
difficulty: 'hard',
removedCells: 45,
timeLimit: 480 // 8 minutes
})
`#### Returns: GameManager
`typescript
type GameManager = {
// DOM Reference
containerRef: RefObject;
// Rendering
renderBoard: () => void;
// Game Status
gameStatus: GameStatus;
setGameStatus: (status: GameStatus) => void;
startGame: () => void;
togglePause: (pause?: boolean) => void;
quitGame: () => void;
// Cursor
cursor: CursorManager;
// Scoring
scoreManager: ScoreManager;
// Input Tracking
keyLog: KeyLogEntry[];
clearKeyLog: () => void;
getKeyLog: () => KeyLogEntry[];
};
`#### CursorManager
`typescript
type CursorManager = {
position: () => Coord; // Current { row, col }
mode: () => CursorMode; // 'normal' | 'edit'
moveLeft: (count?: number) => void;
moveRight: (count?: number) => void;
moveUp: (count?: number) => void;
moveDown: (count?: number) => void;
moveToStart: () => void; // ^ or 0
moveToEnd: () => void; // $
moveToTop: () => void; // gg
moveToBottom: () => void; // G
repeatLastMotion: () => void; // .
};
`#### ScoreManager
`typescript
type ScoreManager = {
timeValue: number; // Milliseconds elapsed
startTimer: () => void;
stopTimer: () => void;
resetTimer: () => void;
totalKeystrokes: number; // All keys pressed
hintsUsed: number; // Number of hints requested
finalScore: number | null; // 0-1000, null until game-won
gameOverReason: string | null; // "Time's up!" or "Too many hints!"
};
`---
Game States
The game follows a strict state machine:
`
waiting โ started โ game-won
โ
game-over
All states โ [quit] โ waiting
started โ paused
`$3
| State | Description | Triggers |
|-------|-------------|----------|
|
waiting | Initial state, awaiting start | Default on load |
| started | Game in progress | Press Space or call startGame() |
| paused | Game temporarily paused | Press P or call togglePause() |
| game-won | Puzzle completed successfully | All cells filled correctly |
| game-over | Failed to complete in time/hints | Time limit or hint limit exceeded |---
VIM Controls
$3
#### Movement
| Key | Action | Example |
|-----|--------|---------|
|
h | Move left | h moves 1 left |
| j | Move down | j moves 1 down |
| k | Move up | k moves 1 up |
| l | Move right | l moves 1 right |
| | Move with count | 5j moves 5 down, 3l moves 3 right |
| 0 or ^ | Jump to row start | Move to column 0 |
| $ | Jump to row end | Move to column 8 |
| gg | Jump to board top | Move to row 0 |
| G | Jump to board bottom | Move to row 8 |
| . | Repeat last motion | Repeats with same count |#### Edit Commands
| Key | Action | Valid On | Mode |
|-----|--------|----------|------|
|
i | Insert digit | โ
Empty cells only | Multi-edit: type digits until Esc |
| r | Replace digit | โ
User-entered cells only | Single-edit: auto-exit after 1 digit |
| c | Change digit | โ
User-entered cells only | Single-edit: auto-exit after 1 digit |
| x | Delete digit | โ
User-entered cells only | Instant |
| d | Delete digit | โ
User-entered cells only | Instant |
| Delete | Delete digit | โ
User-entered cells only | Instant |
| Backspace | Invalid move | โ All cells | Red flash (use in edit mode) |#### Hints & Game Control
| Key | Action | Notes |
|-----|--------|-------|
|
Shift+H | Request hint | Penalty: 25/50/100 pts based on difficulty |
| q | Quit game | Return to waiting state |
| p | Pause/unpause | Toggle pause state |
| Space | Start new game | Only in waiting/game-over state |$3
| Key | Action |
|-----|--------|
|
1-9 | Enter digit in current cell |
| Backspace | Clear current cell (stay in edit mode) |
| Escape | Exit to normal mode |---
Cell State Rules
Understanding cell states is crucial for VIM-style editing:
| Cell State |
i Insert | r/c Replace | x/d/Delete | Visual Class |
|------------|------------|-----------------|------------------|--------------|
| Empty | โ
Enter multi-edit | ๐ด Flash (use i) | ๐ด Flash (nothing to delete) | None |
| User-entered | ๐ด Flash (use r/c) | โ
Enter single-edit | โ
Delete instantly | .user-entered |
| Given | ๐ด Flash (locked) | ๐ด Flash (locked) | ๐ด Flash (locked) | .given |$3
- ๐ต Blue outline โ Normal mode cursor (
.active)
- ๐ก Yellow pulsing glow โ Edit mode cursor (.editing)
- ๐ด Red flash โ Invalid move (.invalid-move - 500ms)
- ๐ด Red flash + background โ Hint: incorrect cell (.hint-flash-error - 1000ms)
- ๐ข Green flash + background โ Hint: correct digit shown (.hint-flash-correct - 1000ms)---
Scoring System
$3
`
Base Score = 1000 - (time penalty) - (keystroke penalty) - (hint penalty)
Final Score = min(1000, max(0, round(Base Score ร difficulty multiplier)))
`$3
Time Penalty:
seconds / 10
- 10 seconds = -1 point
- 60 seconds = -6 points
- 300 seconds = -30 pointsKeystroke Penalty:
totalKeystrokes / 2
- 2 keystrokes = -1 point
- 50 keystrokes = -25 points
- 200 keystrokes = -100 pointsHint Penalty:
hintsUsed ร penalty
- Easy: 25 points per hint
- Medium: 50 points per hint
- Hard: 100 points per hint$3
- Easy: 1.0x (no bonus)
- Medium: 1.5x (can exceed 1000 base, capped at 1000)
- Hard: 2.0x (can exceed 1000 base, capped at 1000)
$3
Easy Mode (25 cells, 1.0x):
`
30 seconds, 50 keys, 0 hints:
= 1000 - 3 - 25 - 0 = 972 ร 1.0 = 972 / 1000
`Hard Mode (50 cells, 2.0x):
`
120 seconds, 150 keys, 2 hints:
= 1000 - 12 - 75 - 200 = 713 ร 2.0 = 1000 / 1000 (capped)
`---
Game Over Conditions
$3
- Default: 600 seconds (10 minutes) for all difficulties
- Configurable: Set via
GameOptions.timeLimit
- Trigger: When timeValue >= timeLimit ร 1000
- Message: "Time's up!"$3
- Threshold: 500 points total hint penalty
- Easy: 20 hints max (20 ร 25 = 500)
- Medium: 10 hints max (10 ร 50 = 500)
- Hard: 5 hints max (5 ร 100 = 500)
- Trigger: When
hintsUsed ร HINT_PENALTY[difficulty] >= 500
- Message: "Too many hints!"Both conditions checked continuously during gameplay. Timer stops on game-over.
---
Hint System
Press
Shift+H in normal mode to request a hint.$3
Priority 1: Show Incorrect Cell (Red Flash)
- Finds all user-entered cells with wrong values
- Selects one randomly
- Flashes red with background for 1 second
- Does NOT reveal correct digit (player must figure it out)
Priority 2: Show Correct Digit (Green Flash)
- If no incorrect cells exist
- Finds all empty cells
- Selects one randomly
- Shows correct digit with green flash for 1 second
- Digit disappears after flash (player must remember and enter it)
$3
Hints subtract directly from base score before multiplier:
- Easy: -25 points per hint
- Medium: -50 points per hint
- Hard: -100 points per hint
After 500 points of penalties, game-over triggers.
---
Configuration
$3
Beginner Practice:
`typescript
useGame({
difficulty: 'easy',
removedCells: 15,
timeLimit: 900 // 15 minutes
})
`Standard Easy:
`typescript
useGame({ difficulty: 'easy' })
// 25 cells, 10 minutes, 1.0x multiplier
`Standard Medium:
`typescript
useGame({ difficulty: 'medium' })
// 40 cells, 10 minutes, 1.5x multiplier
`Standard Hard:
`typescript
useGame({ difficulty: 'hard' })
// 50 cells, 10 minutes, 2.0x multiplier
`Speed Challenge:
`typescript
useGame({
difficulty: 'medium',
timeLimit: 300 // 5 minutes
})
`Custom Difficulty:
`typescript
useGame({
difficulty: 'easy', // Easy scoring (1.0x, 25pt hints)
removedCells: 60, // But very hard puzzle
timeLimit: 1200 // Generous time (20 min)
})
`---
Game Instructions Export
The package exports a structured
gameInfo object containing complete game documentation:`typescript
import { gameInfo } from '@vimazing/vim-sudoku';// Access structured instructions
console.log(gameInfo.name); // "VIM Sudoku"
console.log(gameInfo.controls); // Navigation, editing, deletion, hints, game
console.log(gameInfo.rules); // Cell types, modes, visual feedback
console.log(gameInfo.scoring); // Formula, penalties, multipliers, examples
console.log(gameInfo.gameOver); // Time and hint limit conditions
console.log(gameInfo.hints); // How the hint system works
console.log(gameInfo.objective); // Win condition
`Use cases:
- Render in-game help screens
- Generate tutorials
- Display control reference
- Show scoring breakdown
- Explain game mechanics
All data is fully typed with the
GameInfo type for type safety.---
Example App
A demo application lives under
example/ and consumes the package directly.`bash
cd example
npm install
npm run dev
`The example shows:
- Difficulty selection (Easy/Medium/Hard)
- Live scoreboard (Time, Keystrokes, Hints)
- Game status messages
- Final score display on win
- All vim controls working
---
Platform Hook
Optional callback for platform integration:
`typescript
function myPlatformHook(gameManager: GameManager) {
// Track analytics
console.log('Game initialized');
// Add custom key handlers
window.addEventListener('keydown', (e) => {
if (e.key === 'F1') {
console.log('Help requested');
}
});
// Monitor game events
const interval = setInterval(() => {
if (gameManager.gameStatus === 'game-won') {
console.log('Victory!', gameManager.scoreManager.finalScore);
clearInterval(interval);
}
}, 100);
}const gameManager = useGame({ difficulty: 'easy' }, myPlatformHook);
``---
MIT ยฉ Andrรฉ Padez