# @vimazing/vim-sudoku [![npm version](https://img.shields.io/npm/v/@vimazing/vim-sudoku.svg)](https://www.npmjs.com/package/@vimazing/vim-sudoku) [![npm downloads](https://img.shields.io/npm/dm/@vimazing/vim-sudoku.svg)](https://www.npmjs.com/package/@vimazing/vim-sudoku) [![license](https://img.shields.io/npm/l/@vimazing/vim-sudoku.svg)](https://github.com/vimazing/vim-sudoku/blob/main/LICENSE) ![VIM Sudoku Demo](./vim-sudoku.gif) Lightweight, typed **React hooks** and utilities for building interactive sudoku games with **VIM-style modal editing**. Part of the [VIMazing](https://vimazing.com) project - [GitHub](https://github.com/andrepadez/vimazing-vimaze). --- ## Contents - [Features](#features) - [Installation](#installation) - [Quick Start](#quick-start) - [VIM Modal Editing](#vim-modal-editing) - [Core Hooks](#core-hooks) - [Game Mechanics](#game-mechanics) - [Example App](#example-app) - [Build & Release](#build--release) - [License](#license) --- ## Features - ๐ŸŽฎ **VIM modal editing** โ€“ Navigate in normal mode, edit cells in insert mode (R/C to enter) - โŒจ๏ธ **Full VIM motions** โ€“ `h`/`j`/`k`/`l`, counted moves, `^`/`$`/`0` for row navigation - ๐ŸŽฏ **Smart cell locking** โ€“ Pre-filled clues are protected with visual feedback - โฑ๏ธ **Time & keystroke limits** โ€“ Configurable game-over conditions - ๐Ÿ“Š **Built-in scoring** โ€“ Time-based scoring with keystroke tracking - ๐ŸŽจ **Tokyo Night theme** โ€“ Beautiful dark theme with 3x3 box separators - ๐Ÿ“ฆ **Typed API** โ€“ Full TypeScript support with generated declarations - ๐Ÿช **Composable hooks** โ€“ Separate concerns for game state, rendering, input, and scoring - ๐ŸŒฒ **Tree-shakeable exports** โ€“ Import only what you need --- ## Installation Using **npm**: ```bash npm install @vimazing/vim-sudoku ``` Or with **bun**: ```bash bun add @vimazing/vim-sudoku ``` --- ## Quick Start ```tsx import { useGame } from "@vimazing/vim-sudoku"; import "@vimazing/vim-sudoku/game.css"; export function SudokuGame() { const { containerRef, initGame, gameStatus, scoreManager } = useGame(); return (

VIMazing Sudoku

{gameStatus === "game-won" && (

๐ŸŽ‰ Score: {scoreManager.finalScore?.toLocaleString()}

)}
); } ``` > **Note:** You must manually import the CSS file. The package exports styles but does not auto-import them, giving you control over when and how styles are loaded. --- ## VIM Modal Editing VIMazing Sudoku follows VIM's modal editing philosophy with strict insert/replace semantics: ### Navigation Mode (default) #### Movement | Key | Action | Notes | | --- | ------ | ----- | | `h` / `j` / `k` / `l` | Move cursor left/down/up/right | Core VIM navigation | | `h/j/k/l` | Move with count | e.g., `3j` = down 3, `5l` = right 5 | | `0` / `^` | Jump to start of row | Move to column 0 | | `$` | Jump to end of row | Move to column 8 | | `.` | Repeat last motion | Repeat with optional new count | #### Editing | Key | Action | Valid On | | --- | ------ | -------- | | `I` | Enter insert mode | โœ… Empty cells only | | `R` | Enter replace mode | โœ… User-filled cells only | | `D` / `X` | Delete cell value | โœ… User-filled cells only | #### Game Control | Key | Action | | --- | ------ | | `Q` | Quit game | ### Edit Mode (I/R to enter) | Key | Action | | --- | ------ | | `1-9` | Fill cell with number | | `x` / `Backspace` | Clear cell | | `Esc` / `Enter` | Exit edit mode back to navigation | --- ## Cell State Rules Understanding cell states is crucial for VIM-style editing: | Cell State | `I` (Insert) | `R` (Replace) | `D`/`X` (Delete) | Visual | | ---------- | ------------ | ------------- | ---------------- | ------ | | **Empty** (`.`) | โœ… Enter edit | ๐Ÿ”ด Flash (use I) | โž– No effect | Normal | | **User-filled** | ๐Ÿ”ด Flash (use R) | โœ… Enter edit | โœ… Delete | Normal | | **Pre-filled clue** | ๐Ÿ”ด Flash (locked) | ๐Ÿ”ด Flash (locked) | ๐Ÿ”ด Flash (locked) | Darker text | ### Visual Feedback - ๐Ÿ”ต **Blue glow** โ€“ Navigation mode (current cell) - ๐ŸŸก **Yellow pulsing glow** โ€“ Edit mode (typing active) - ๐Ÿ”ด **Red flash** โ€“ Invalid action (wrong mode, locked cell, or out of bounds) ### Examples ``` Empty cell: Press I โ†’ type 5 โ†’ Esc Replace value: Press R โ†’ type 7 โ†’ Esc Delete value: Press D (instant) Move 3 down: Press 3j Jump to end: Press $ ``` --- ## Core Hooks ### `useGame(platformHook?, options?)` Main hook that orchestrates the entire sudoku game. **Options:** - `validateMoves?: boolean` (default: `false`) - Enable real-time move validation with conflict detection. When `false`, allows harder gameplay without immediate feedback. **Returns:** ```ts { // Core containerRef: RefObject, gameStatus: GameStatus, // "waiting" | "started" | "game-won" | "game-over" startGame: () => void, stopGame: () => void, initGame: () => void, // Reset board + start // Cursor cursor: { r: number; c: number }, moveCursor: (dr, dc, steps?) => void, // Board board: string, // 81-character string initialBoard: string, // Original clues setDigit: (digit) => void, erase: () => void, reset: () => void, solveBoard: () => void, boardToString: (human?) => string, // Format board for display // Scoring scoreManager: { timeValue: number, // Time in milliseconds finalScore: number | null, timeLimit: number, // 60000ms (60 seconds) keystrokeLimit: number, // 100 keystrokes }, // Key logging keyLog: KeyLogEntry[], editMode: "navigation" | "edit", } ``` ### Individual Hooks Export individual hooks for granular control: ```ts import { useScore } from "@vimazing/vim-sudoku/useScore"; import { useBoardState } from "@vimazing/vim-sudoku/useGame/useBoardState"; ``` --- ## Game Mechanics ### Scoring Scoring formula with time and move penalties: ``` score = max(0, 100000 - timeInSeconds ร— 10 - extraMoves ร— 50) ``` Where: - `baseScore = 100000` - `timePenalty = timeInSeconds ร— 10` - `extraMoves = actualKeystrokes - minMovesRequired` - `movePenalty = extraMoves ร— 50` ### Game Over Conditions - โฑ๏ธ **Time limit**: 60 seconds - โŒจ๏ธ **Keystroke limit**: 100 keystrokes Customize in `src/useScore/useScore.ts`: ```ts const TIME_LIMIT_MS = 60000; const KEYSTROKE_LIMIT = 100; ``` ### Win Detection Automatic win detection is fully implemented using an efficient sum-to-45 algorithm: - Validates when board is complete (no empty cells) - Checks all rows sum to 45 (1+2+...+9) - Checks all columns sum to 45 - Checks all 3x3 boxes sum to 45 Manual win trigger available via `solveBoard()` for testing. ### Board Format ```ts // Computer format (compact) boardToString() // "628.1...535928671..." // Human format (readable) boardToString(true) // 6 2 8 | 1 . . | . 5 . // 5 3 9 | 2 4 8 | 6 7 1 // ... ``` --- ## Example App A demo application lives under `example/` and consumes the package directly. ```bash cd example bun install bun run dev ``` During local development the Vite config aliases `@vimazing/vim-sudoku` to the source folder so you can iterate without rebuilding. When publishing, run the build first (see below) so editors consume the generated declarations in `dist/`. --- ## Build & Release Build the distributable bundle and type declarations: ```bash bun run build ``` This writes JavaScript, type definitions, and styles to `dist/`. The `prepublishOnly` hook reuses the same command to guarantee fresh artifacts before publishing. --- ## License MIT ยฉ [Andrรฉ Padez](https://github.com/andrepadez)