# @vimazing/vim-maze

Lightweight, typed **React hooks** and utilities for building interactive maze games with VIM-inspired controls.
Part of the [VIMazing](https://vimazing.com) project - [GitHub](https://github.com/andrepadez/vimazing-vimaze).
---
## Contents
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Overview](#api-overview)
- [Unified Architecture](#unified-architecture)
- [Example App](#example-app)
- [Documentation](#documentation)
- [Build & Release](#build--release)
- [License](#license)
- [Acknowledgements](#acknowledgements)
---
## Features
- **Unified API** – Single entry point `useGame()` for complete game setup with composition hooks for granular control
- **Fully Typed** – Comprehensive TypeScript support with organized, module-specific type definitions
- **VIM-style Navigation** – Full `hjkl` motion support with counts, anchors (`^`, `$`, `g`, `G`), and repeat (`.`)
- **Modular Hooks** – Independent, reusable hooks: `useBoard`, `useCursor`, `useScore`, `useGameStatus`
- **Scoring System** – Built-in timer, efficiency tracking, keystroke logging, and optimal path calculation
- **Platform Integration** – Optional platform hook for analytics, custom bindings, and platform-specific logic
- **Tree-shakeable Exports** – Import only what you need to keep bundles lean
- **CSS Included** – Pre-styled maze rendering with optional coordinate display
---
## Installation
Using **npm**:
```bash
npm install @vimazing/vim-maze
```
Or with **bun**:
```bash
bun add @vimazing/vim-maze
```
---
## Quick Start
### Minimal Setup
```tsx
import { useGame } from "@vimazing/vim-maze";
import "@vimazing/vim-maze/game.css";
function MazeGame() {
const gameManager = useGame({ cols: 32, rows: 24 });
const { containerRef, gameStatus, startGame } = gameManager;
return (
Status: {gameStatus}
);
}
```
### With Scoring and Full Features
```tsx
import { useGame } from "@vimazing/vim-maze";
import "@vimazing/vim-maze/game.css";
function MazeGame() {
const gameManager = useGame({ cols: 32, rows: 24 });
const { containerRef, gameStatus, startGame, scoreManager } = gameManager;
const { timeValue, efficiency, keystrokes, optimalSteps, finalScore } = scoreManager;
return (
{(gameStatus === 'started' || gameStatus === 'has-key') && (
Time: {(timeValue / 1000).toFixed(1)}s
Efficiency: {efficiency}%
Keystrokes: {keystrokes} / {optimalSteps}
)}
{gameStatus === 'game-won' &&
🎉 Final Score: {finalScore}
}
);
}
```
> **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.
### Default Controls
| Key | Action |
| --- | --- |
| `Space` | Start game / Restart |
| `h` / `j` / `k` / `l` | Move left / down / up / right |
| `^` | Move to row start |
| `$` | Move to row end |
| `g` | Move to maze top |
| `G` | Move to maze bottom |
| `.` | Repeat last motion |
| `` | Move with count (e.g., `5j` = down 5) |
| `q` | Quit game |
---
## API Overview
### Main Entry Point: `useGame()`
Initialize a complete game with a single call:
```tsx
const gameManager = useGame(
{ cols: 32, rows: 24 }, // GameOptions
platformHook // Optional: function for platform integration
);
```
**Returns: `GameManager`**
- `containerRef` – DOM ref for maze rendering
- `gameStatus` – Current game state
- `startGame()` / `togglePause()` / `quitGame()` – Game lifecycle
- `cursor` – Player movement manager
- `hero` – Player position and interactions
- `scoreManager` – Scoring metrics
- `renderer` – Hero rendering manager
- `keyLog` – Keystroke history
### Core Hooks
| Hook | Purpose |
| --- | --- |
| `useBoard(cols, rows)` | Generates maze, manages board data, handles DOM rendering |
| `useCursor(board, gameStatus)` | Input handling, vim motion processing, movement commands |
| `useScore({ board, hero, gameStatusManager, keyManager })` | Timer, efficiency, keystroke tracking, score calculation |
| `useGameStatus()` | Game state machine (waiting → started → has-key → game-won) |
### Utility Functions & Classes
```ts
import { MazeGenerator, MazeRenderer } from "@vimazing/vim-maze";
// Procedural maze generation
const generator = new MazeGenerator(32, 24);
generator.placeKey();
const maze = generator.maze;
// DOM rendering for mazes
const renderer = new MazeRenderer(generator.getData(), true);
renderer.display(containerElement);
```
### Types
All types are organized by module:
```ts
import type {
GameManager,
GameStatus,
GameOptions,
Coord,
CursorManager,
HeroManager,
ScoreManager,
BoardManager,
} from "@vimazing/vim-maze";
// Or import directly from module types:
import type { CursorManager } from "@vimazing/vim-maze/useCursor";
import type { BoardManager } from "@vimazing/vim-maze/useBoard";
```
---
## Unified Architecture
The library uses a **composable hook architecture** with modular organization:
```
useGame()
├─ useBoard() → maze generation & rendering
├─ useCursor() → vim motions & input handling
│ ├─ useHero() → player position & interactions
│ └─ useVimMotions() → vim keybinding processor
├─ useGameStatus() → game lifecycle state
└─ useScore() → timing & scoring metrics
```
### Type Organization
- **`useBoard/types.ts`** – Coord, MazeCell, MazeData, BoardManager
- **`useCursor/types.ts`** – CursorManager, HeroManager, CursorMode, VimMotionSystem
- **`useGameStatus/types.ts`** – GameStatus, GameStatusManager
- **`useScore/types.ts`** – ScoreManager, TimerManager
- **`types.ts`** – Re-exports all + useGame-specific types (GameOptions, GameManager, KeyLogEntry)
Each module's `types.ts` keeps types colocated with their logic, making the architecture easy to understand and extend.
### Platform Integration
Integrate with your platform using an optional platform hook:
```tsx
function platformHook(gameManager: GameManager) {
// Track analytics
console.log('Game initialized');
// Add custom key handlers
window.addEventListener('keydown', (e) => {
if (e.key === 'p') gameManager.togglePause();
});
// Monitor game state
const interval = setInterval(() => {
if (gameManager.gameStatus === 'game-won') {
console.log('Victory!', gameManager.scoreManager.finalScore);
clearInterval(interval);
}
}, 100);
}
const gameManager = useGame({ cols: 32, rows: 24 }, platformHook);
```
---
## Example App
A complete demo application lives under `example/` showcasing the full API:
```bash
cd example
bun install
bun run dev
```
Open `http://localhost:5173` to play the game with scoring, efficiency tracking, and status display.
During local development, the Vite config aliases `@vimazing/vim-maze` to source files for hot reloading. When publishing, run the build first so TypeScript declarations are generated.
---
## Documentation
For comprehensive documentation of all hooks, types, and patterns, see **[UNIFIED_API.md](./UNIFIED_API.md)**.
Key sections:
- Architecture Overview & Core Composition
- Complete Type System Reference
- Detailed Hook Documentation
- Implementation Patterns & Examples
- Integration Guide for New Games
---
## 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 runs this automatically before publishing.
To publish a new version:
```bash
npm version patch # or minor / major
npm publish
```
---
## License
MIT © [André Padez](https://github.com/andrepadez)
---
## Acknowledgements
Inspired by [The Art of Web – Random Maze Generator](https://www.the-art-of-web.com/javascript/maze-game/) and extended for the VIMazing platform.