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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vimazing/vim-maze

v3.0.0

Published

VIMazing VIM Maze engine — a lightweight, typed React hook set for maze-based games.

Readme

@vimazing/vim-maze

npm version npm downloads license

VIMazing maze demo

Lightweight, typed React hooks for building interactive maze games with VIM-style navigation.

Part of the VIMazing project.


Contents


Features

  • 🎮 VIM navigation – Full hjkl movement with counts, anchors, and repeat
  • 🗺️ Procedural mazes – Algorithm-generated mazes with guaranteed solvability
  • ⌨️ Complete VIM motions – hjkl, counts (5j), anchors (^/$), gg/G, repeat (.)
  • 🎯 Maze navigation – Find key, reach exit, with wall collision detection
  • ⏱️ Time-based gameplay – Configurable time limits with game-over on timeout
  • 📊 Simple scoring – Time + keystrokes with maze size multiplier
  • 🎨 Tokyo Night theme – Beautiful dark theme with clear visual feedback
  • 📦 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-maze

Or with bun:

bun add @vimazing/vim-maze

Quick Start

import { useGame } from "@vimazing/vim-maze";
import "@vimazing/vim-maze/game.css";

export function MazeGame() {
  const gameManager = useGame({ 
    rows: 24, 
    cols: 32,
    timeLimit: 600  // 10 minutes
  });
  
  const { containerRef, gameStatus, scoreManager, startGame } = gameManager;

  return (
    <div>
      <h1>VIMazing Maze</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 = {
  rows: number;         // Maze height in cells
  cols: number;         // Maze width in cells  
  timeLimit?: number;   // In seconds, default: 600 (10 min)
};

Examples:

// Small maze
useGame({ rows: 16, cols: 24 })

// Large maze with time pressure
useGame({ 
  rows: 32, 
  cols: 48,
  timeLimit: 300  // 5 minutes
})

// Custom configuration
useGame({ 
  rows: 24,
  cols: 32,
  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 (Hero)
  cursor: CursorManager;
  hero: HeroManager;
  renderer: HeroRenderManager;
  
  // Scoring
  scoreManager: ScoreManager;
  
  // Input Tracking
  keyLog: KeyLogEntry[];
  clearKeyLog: () => void;
  getKeyLog: () => KeyLogEntry[];
};

CursorManager

type CursorManager = {
  position: () => Coord;              // Current { row, col }
  mode: () => CursorMode;             // 'normal' | 'insert'
  move: (dCols: number, dRows: number, count: number) => void;
  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;       // .
  resetCount: () => void;
  getCount: () => string;
  hasCount: () => boolean;
  hero?: HeroManager;
};

HeroManager

type HeroManager = {
  heroPos: Coord | null;              // Hero location
  canMoveTo: (coord: Coord) => boolean;
  moveTo: (coord: Coord) => void;
  pickupKey: () => void;
  reachExit: () => void;
  reset: () => void;
};

ScoreManager

type ScoreManager = {
  timeValue: number;                  // Milliseconds elapsed
  startTimer: () => void;
  stopTimer: () => void;
  resetTimer: () => void;
  distToKey: number;                  // Distance to key
  distToExit: number;                 // Distance to exit
  keystrokes: number;                 // Total keys pressed
  finalScore: number | null;          // 0-1000, null until game-won
};

Game States

The game follows a strict state machine:

waiting → started → has-key → game-won
              ↓
           game-over
              
All states → [quit] → waiting
started/has-key ↔ paused

State Descriptions

| State | Description | Triggers | |-------|-------------|----------| | waiting | Initial state, awaiting start | Default on load | | started | Game in progress, searching for key | Press Space or call startGame() | | has-key | Key obtained, heading to exit | Hero reaches key cell | | paused | Game temporarily paused | Press P or call togglePause() | | game-over | Failed to complete in time | Time limit exceeded | | game-won | Successfully reached exit with key | Hero reaches exit after getting key |


VIM Controls

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, 10l moves 10 right | | 0 or ^ | Jump to row start | Move to leftmost walkable cell | | $ | Jump to row end | Move to rightmost walkable cell | | gg | Jump to maze top | Move to topmost walkable row | | G | Jump to maze bottom | Move to bottommost walkable row | | . | Repeat last motion | Repeats with same count |

Game Control

| Key | Action | Notes | |-----|--------|-------| | q | Quit game | Return to waiting state | | p | Pause/unpause | Toggle pause state | | Space | Start new game | Only in waiting/game-over state |

Movement Rules

  • Wall collision: Movement stops at walls, no wrapping
  • Counted moves: Multi-step movements (e.g., 5j) stop at first wall
  • Key pickup: Automatic when hero reaches key cell
  • Exit: Can only enter after obtaining key

Scoring System

Formula

Base Score = 1000 - (time penalty) - (keystroke penalty)
Size Multiplier = max(1.0, (rows × cols) / 500)
Final Score = min(1000, max(0, round(Base Score × Size Multiplier)))

Penalties

Time Penalty: seconds / 10

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

Keystroke Penalty: keystrokes / 2

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

Size Multiplier

Rewards larger, more complex mazes:

  • Small maze (16×24 = 384 cells): 1.0x multiplier
  • Medium maze (24×32 = 768 cells): 1.54x multiplier
  • Large maze (32×48 = 1536 cells): 3.07x multiplier

Example Scores

Small Maze (16×24):

60 seconds, 80 keys:
= 1000 - 6 - 40 = 954 × 1.0 = 954 / 1000

Medium Maze (24×32):

120 seconds, 150 keys:
= 1000 - 12 - 75 = 913 × 1.54 = 1000 / 1000 (capped)

Large Maze (32×48):

180 seconds, 200 keys:
= 1000 - 18 - 100 = 882 × 3.07 = 1000 / 1000 (capped)

Game Over Conditions

Time Limit

  • Default: 600 seconds (10 minutes)
  • Configurable: Set via GameOptions.timeLimit
  • Trigger: When timeValue >= timeLimit × 1000
  • States: Checked during started and has-key states

No Other Limits

Unlike vim-sudoku, vim-maze has no hint system or additional penalties. The only way to lose is running out of time.


Configuration

Recommended Configurations

Beginner:

useGame({ 
  rows: 12,
  cols: 16,
  timeLimit: 900  // 15 minutes
})

Standard Small:

useGame({ 
  rows: 16,
  cols: 24,
  timeLimit: 600  // 10 minutes
})

Standard Medium:

useGame({ 
  rows: 24,
  cols: 32,
  timeLimit: 600  // 10 minutes
})

Standard Large:

useGame({ 
  rows: 32,
  cols: 48,
  timeLimit: 600  // 10 minutes
})

Speed Challenge:

useGame({
  rows: 24,
  cols: 32,
  timeLimit: 300  // 5 minutes
})

Marathon:

useGame({
  rows: 48,
  cols: 64,
  timeLimit: 1200  // 20 minutes
})

Game Instructions Export

The package exports a structured gameInfo object containing complete game documentation:

import { gameInfo } from '@vimazing/vim-maze';

// Access structured instructions
console.log(gameInfo.name);           // "VIM Maze"
console.log(gameInfo.controls);       // Navigation, game controls
console.log(gameInfo.rules);          // Movement, game flow, maze elements
console.log(gameInfo.scoring);        // Formula, penalties, size multiplier, examples
console.log(gameInfo.gameOver);       // Time limit condition
console.log(gameInfo.mazeGeneration); // Algorithm, guarantees, placement
console.log(gameInfo.metrics);        // Tracked metrics during gameplay
console.log(gameInfo.objective);      // Win condition

Use cases:

  • Render in-game help screens
  • Generate tutorials
  • Display control reference
  • Show scoring breakdown
  • Explain maze 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:

  • Maze size configuration
  • Live scoreboard (Time, Keystrokes, Distances)
  • 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('Maze initialized:', gameManager.hero.heroPos);
  
  // 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 === 'has-key') {
      console.log('Key obtained!');
    }
    if (gameManager.gameStatus === 'game-won') {
      console.log('Victory!', gameManager.scoreManager.finalScore);
      clearInterval(interval);
    }
  }, 100);
}

const gameManager = useGame(
  { rows: 24, cols: 32 }, 
  myPlatformHook
);

Maze Generation

Mazes are procedurally generated using a depth-first search algorithm with guaranteed solvability:

  • Entrance: Always top-left area
  • Key: Placed in maze requiring navigation
  • Exit: Always bottom-right area
  • Paths: Guaranteed path from entrance → key → exit
  • Walls: Procedurally generated with no isolated areas

License

MIT © André Padez


Acknowledgements

Inspired by The Art of Web – Random Maze Generator and extended for the VIMazing platform.