npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@vimazing/vim-sudoku

v3.0.0

Published

VIMazing VIM Sudoku engine — a lightweight, typed React hook set for sudoku games.

Downloads

82

Readme

@vimazing/vim-sudoku

npm version npm downloads license

VIM Sudoku Demo

Lightweight, typed React hooks for building interactive sudoku games with VIM-style modal editing.

Part of the VIMazing project.


Contents


Features

  • 🎮 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

Installation

npm install @vimazing/vim-sudoku

Or with bun:

bun add @vimazing/vim-sudoku

Quick Start

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 (
    <div>
      <h1>VIMazing Sudoku</h1>
      
      {gameStatus === 'waiting' && (
        <button onClick={startGame}>Start Game</button>
      )}
      
      <div ref={containerRef} />
      
      {gameStatus === 'game-won' && (
        <div>
          <h2>You Won!</h2>
          <p>Score: {scoreManager.finalScore} / 1000</p>
          <p>Time: {Math.floor(scoreManager.timeValue / 1000)}s</p>
        </div>
      )}
    </div>
  );
}

Note: You must manually import game.css for styling.


API Reference

useGame(options?, platformHook?)

Main orchestrator hook that composes all game functionality.

Options

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:

// 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

type GameManager = {
  // DOM Reference
  containerRef: RefObject<HTMLDivElement | null>;
  
  // 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

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

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

State Descriptions

| 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

Normal Mode (Default)

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 | | <count><motion> | 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 |

Edit Mode (i/r/c to enter)

| 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 |

Visual Feedback

  • 🔵 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

Formula

Base Score = 1000 - (time penalty) - (keystroke penalty) - (hint penalty)
Final Score = min(1000, max(0, round(Base Score × difficulty multiplier)))

Penalties

Time Penalty: seconds / 10

  • 10 seconds = -1 point
  • 60 seconds = -6 points
  • 300 seconds = -30 points

Keystroke Penalty: totalKeystrokes / 2

  • 2 keystrokes = -1 point
  • 50 keystrokes = -25 points
  • 200 keystrokes = -100 points

Hint Penalty: hintsUsed × penalty

  • Easy: 25 points per hint
  • Medium: 50 points per hint
  • Hard: 100 points per hint

Difficulty Multipliers

  • 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)

Example Scores

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

Time Limit

  • Default: 600 seconds (10 minutes) for all difficulties
  • Configurable: Set via GameOptions.timeLimit
  • Trigger: When timeValue >= timeLimit × 1000
  • Message: "Time's up!"

Hint Limit

  • 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.

Hint Priority

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)

Hint Penalties

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

Recommended Configurations

Beginner Practice:

useGame({ 
  difficulty: 'easy',
  removedCells: 15,
  timeLimit: 900  // 15 minutes
})

Standard Easy:

useGame({ difficulty: 'easy' })
// 25 cells, 10 minutes, 1.0x multiplier

Standard Medium:

useGame({ difficulty: 'medium' })
// 40 cells, 10 minutes, 1.5x multiplier

Standard Hard:

useGame({ difficulty: 'hard' })
// 50 cells, 10 minutes, 2.0x multiplier

Speed Challenge:

useGame({
  difficulty: 'medium',
  timeLimit: 300  // 5 minutes
})

Custom Difficulty:

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:

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.

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:

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);

License

MIT © André Padez