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

dots-engine

v0.1.0-alpha.0

Published

js engine for dots game

Downloads

38

Readme

dots-engine

A TypeScript engine for the classic "Dots" game (a.k.a. Dots and Boxes) — a paper-and-pencil strategy game, played here as a strict 1v1 turn-based match.

The engine is designed to be deterministic: given the same sequence of moves, it always produces the same result, which makes it suitable for decentralized applications as well as any ordinary client/server setup.

About the Game

  • Start with a grid of dots.
  • Players take turns connecting two adjacent dots with a horizontal or vertical line.
  • When a move completes a square (its fourth edge), that square is owned by whoever drew the line, and that player gets an extra move.
  • The game ends when no more lines can be drawn.
  • The player who owns the most completed squares wins; a tie is a draw.

Features

  • Complete Game Logic: Full implementation of the dots-and-boxes rules.
  • Configurable Grid: Support for different grid sizes (minimum 2x2).
  • Strict 1v1 Matches: Exactly two players per match, alternating turns, with an extra move for the player who closes a square.
  • Score Tracking: Automatic per-player tally of closed squares.
  • King-of-the-Chain Tracking: Each player's longest run of squares closed in one uninterrupted turn.
  • Game State Management: Tracks game status (in progress, over, draw).
  • Move Validation: Enforces turn order, grid bounds, dot adjacency, and rejects already-drawn edges.
  • Move Log: Records every applied move as a JSON-serializable log, ready for a client to replay or persist.
  • Player Identity Normalization: Case-insensitive player IDs, so the same player never forks into separate score buckets.
  • Point-Based Model: Players connect dots using coordinate pairs.
  • Square-Based Logic: Internal square tracking for scoring and game state.

Key Components

  • Dots.ts: Main game class that orchestrates a match — turns, scoring, status, and the move log.
  • Grid.ts: Manages the game board, points, squares, and edges.
  • Edge.ts: Represents connections between dots using coordinate pairs.
  • Point.ts: Represents individual dots on the grid.
  • Square.ts: Represents the squares that can be completed.
  • GameConstants.ts: Game constants and status definitions.
  • address.ts: Normalizes player identifiers so identity is consistent regardless of input casing.

Game Model

The engine uses a hybrid model that matches how the game is actually played:

  • Points/Dots: The primary entities that players interact with.
  • Edges: Connections between adjacent points specified by coordinate pairs.
  • Squares: Internal entities for tracking completed squares and scoring.
  • Coordinate System: Players specify moves as two adjacent dots, [x1, y1] and [x2, y2].

Example: 3x3 Grid

Points: (0,0) (1,0) (2,0)
        (0,1) (1,1) (2,1)
        (0,2) (1,2) (2,2)

Squares: 4 squares arranged as 2x2
Square 0 at (0,0)  Square 1 at (0,1)
Square 2 at (1,0)  Square 3 at (1,1)

Installation

npm install dots-engine

Usage

Basic Example

import { Dots } from "dots-engine";

const alice = "0xAl1ce";
const bob = "0xB0b";

// Create a 3x3 grid (2x2 squares) for a match between alice and bob.
// alice is players[0], so she moves first.
const dots = new Dots(3, [alice, bob], "match-1");

// Each move connects two adjacent dots, submitted by the player on turn.
dots.play([0, 0], [0, 1], alice);
dots.play([0, 0], [1, 0], bob);
dots.play([0, 1], [1, 1], alice);

// play() returns what changed, so you can re-render straight from it.
const result = dots.play([1, 0], [1, 1], bob); // Completes square 0, owned by bob
console.log(result); // { squaresClosed: 1, submitter: "0xb0b", status: 2 }

console.log("Game over:", dots.isOVer());
console.log("Score:", dots.getScore()); // { "0xb0b": 1 }

Advanced Example

import { Dots } from "dots-engine";

const alice = "0xAl1ce";
const bob = "0xB0b";
const dots = new Dots(4, [alice, bob], "match-2"); // Create a 4x4 grid

dots.play([0, 0], [0, 1], alice);
dots.play([0, 0], [1, 0], bob);
dots.play([0, 1], [1, 1], alice);
dots.play([1, 0], [1, 1], bob);

console.log("Game over:", dots.isOVer());
console.log("Score:", dots.getScore());
console.log("Is draw:", dots.isDraw());
console.log("Winner:", dots.getWinner());
console.log("Longest chains:", dots.getLongestChain());
console.log("Move log:", dots.moveLog);

// Get square position from ID
const grid = dots.grid;
const [row, col] = grid.getSquarePosition(5); // Square 5
console.log("Square 5 is at position:", row, col);

// Get square ID from position
const squareId = grid.getSquareId(1, 2); // Position (1,2)
console.log("Position (1,2) corresponds to square:", squareId);

// Show all points in the grid
console.log("All points in the grid:");
for (let point of grid.getPoints()) {
    console.log(`Point: ${point.toString()}`);
}

Frontend usage

The engine speaks plain coordinate tuples, so the same Coord type can describe your UI state and feed play directly — no engine-internal classes to import, and everything is JSON-serializable.

import { Dots, type Coord } from "dots-engine";

const dots = new Dots(3, ["0xAl1ce", "0xB0b"], "match-3");
let selected: Coord | null = null;
const myAddress = "0xAl1ce"; // the connected wallet's address

// Call this whenever the user clicks a dot.
function onDotClick(dot: Coord) {
  if (!selected) {
    selected = dot;          // first click: remember the start dot
    return;
  }
  try {
    const result = dots.play(selected, dot, myAddress); // second click: complete the move
    // re-render from result.submitter / result.status / dots.getScore()
  } catch (err) {
    // not this player's turn / not adjacent / out of bounds — show feedback to the user
  } finally {
    selected = null;
  }
}

A move is always an edge (two dots), so the two-click selection lives in your UI; the engine only ever receives one complete play(from, to, submitter) call.

Determinism

The engine never reads the wall clock or uses randomness — Date.now() and Math.random() are both banned by lint rules — so two independent executions of the same sequence of play() calls always produce the same state, score, and outcome, whether that's in a browser, on a server, or inside a blockchain's execution environment.

The engine itself doesn't timestamp moves, detect a stalled turn, or forfeit a non-responding player. Timing, turn timeouts, and replaying a match's history from a log are all client/orchestration concerns built on top of Dots (e.g. by pairing each play() call with your own timestamp), not things this engine implements.

Coordinate System

  • Dots: A dot is a Coord tuple [x, y], where x is the column and y the row (both 0-based), ranging from [0, 0] to [gridSize-1, gridSize-1].
  • Moves: A move connects two orthogonally adjacent dots (the edge between them).
  • Squares: Internal tracking with IDs 0, 1, 2, … arranged in rows.
  • Validation: play enforces turn order, and that the two dots are adjacent and within grid bounds.

Development

Running Tests

npm test

Running Demo

npm run dev

License

ISC License