miaoda-game-grid-core
v0.2.0
Published
Engine-agnostic grid primitives: typed grid model, direction math, A* pathfinding. No rendering, no engine dependency. Backs match-3, tactics, tower-defense, roguelike and tank-battle style games.
Maintainers
Readme
miaoda-game-grid-core
Use this package when your game state is arranged on rectangular tiles: tactics, tower defense, roguelikes, match-3, Sokoban, minesweeper, tank games, or board-game movement. It provides a typed grid, direction helpers, pathfinding, movement ranges, distance fields, and area shapes without depending on a game engine.
Choose an engine adapter only for presentation and input:
miaoda-game-grid-cocosplaces Cocos nodes and converts taps to tiles.miaoda-game-grid-phaserplaces Phaser Game Objects, converts pointers, and moves characters along paths.miaoda-game-match3-corebuilds match-3 rules on top of this package.
Install
pnpm add miaoda-game-grid-coreCreate and query a grid
import { Dir, Grid } from 'miaoda-game-grid-core';
// 0 = open, 1 = wall
const map = new Grid<number>({ width: 13, height: 13, empty: 0 });
map.set(6, 6, 1);
map.get(6, 6); // 1
map.isEmpty(6, 6); // false
map.neighbor(6, 6, Dir.Up); // { x: 6, y: 5 }
map.neighbors(0, 0); // two in-bounds orthogonal neighborsGrid<T> stores cells in row-major order. Coordinates use { x, y }, with (0, 0) at the top-left, X increasing right, and Y increasing down. Set wrap: true for a grid whose opposite edges connect. On a non-wrapping grid, out-of-bounds reads return empty and out-of-bounds writes are ignored.
Find a path
import { findPath } from 'miaoda-game-grid-core';
const path = findPath(
map,
{ x: 0, y: 0 },
{ x: 12, y: 12 },
{
isBlocked: (x, y) => map.get(x, y) === 1,
cost: (x, y) => terrainCost(x, y),
},
);
if (path && path.length > 1) {
moveUnitToward(path[1]);
}findPath returns the complete path including the start and goal, or null when the goal cannot be reached. The start is treated as passable because the moving unit may already occupy it; the goal must be passable.
Movement is orthogonal by default. Set allowDiagonal: true to use eight directions. Diagonal paths prevent corner cutting by default; set noCornerCutting: false only when units are allowed to pass between touching obstacles. Every movement cost must be at least 1.
Show movement and attack ranges
import { filledRing, findArea } from 'miaoda-game-grid-core';
const reachable = findArea(map, { x: 3, y: 3 }, 4, {
isBlocked: (x, y) => map.get(x, y) === 1,
cost: (x, y) => terrainCost(x, y),
});
for (const { x, y, cost } of reachable) {
showMoveHighlight(x, y, cost);
}
const blast = filledRing({ x: 10, y: 10 }, 2, {
metric: 'manhattan',
filter: (x, y) => map.contains(x, y),
});findArea uses the same blocked, diagonal, and movement-cost model as pathfinding. Each returned tile includes its accumulated cost. Use ring for only the boundary at a radius and filledRing for every tile within it. The manhattan metric produces a diamond; chebyshev produces a square.
Route many units to shared goals
For tower-defense waves or crowds moving toward the same exits, build one distance field and reuse it instead of finding a separate path for every unit.
import { buildDistanceField } from 'miaoda-game-grid-core';
const field = buildDistanceField(
map,
[{ x: 31, y: 8 }, { x: 31, y: 9 }],
{ isBlocked: (x, y) => map.get(x, y) === 1 },
);
const nextTile = field.nextStep(enemy.tileX, enemy.tileY);
const remainingCost = field.distanceAt(enemy.tileX, enemy.tileY);
if (!field.canReach(spawn.x, spawn.y)) {
rejectTowerPlacement();
}Rebuild the field whenever obstacles, exits, or terrain costs change. pathFrom(start) returns a complete path when a renderer or movement controller needs one.
Public API guide
| API | Use it for |
| --- | --- |
| Grid<T> | Cell storage, bounds, wrapping, neighbors, swaps, fills, and iteration |
| Dir, DIR_DELTA, direction sets | Shared four-way and eight-way direction conventions |
| findPath | One shortest path to one goal |
| findArea | All reachable tiles within a movement budget |
| buildDistanceField | Many units routing toward one or more shared goals |
| ring, filledRing | Attack areas, auras, spawn bands, and range previews |
| MinHeap<T> | A reusable priority queue for custom searches or event queues |
The package owns grid data and math only. Your game remains responsible for turns, collision response, rendering, animation, and deciding when a route should be recalculated.
