Skip to content

hemme/hen-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hen-js

A small, dependency-free JavaScript / Node.js library for converting between HEN and SGF formats.

License: MIT JavaScript


What is HEN?

HEN (Hemme Notation) is a lightweight, text-based format designed for efficiently encoding and sharing Go board positions.

The formal grammar is defined in hen-spec (EBNF, MIT license).

SGF (Smart Game Format) is the established standard supported by most Go software. This library bridges the two formats, letting you integrate HEN into existing SGF-based workflows.


Installation

You can install from GitHub:

npm install github:hemme/hen-js

Or use it directly from source (Node >= 18, ESM):

git clone https://github.com/hemme/hen-js.git
cd hen-js

Quick start

import { Hen } from 'hen-js';

// Convert an SGF game to a HEN position after the 10th move.
const henString = Hen.sgf2hen(sgfString, 10);

// Parse a HEN string back into a position object.
const hen = Hen.parse(henString);

// Re-serialize to SGF (setup stones in AB/AW + last move as ;B[]/;W[]).
const sgf = hen.toSgf();

API

Hen.parse(henString) -> Hen | null

Parse a HEN string. Returns null for falsy input. Accepts URL-encoded input (encodeURIComponent is applied transparently).

const hen = Hen.parse('_16b2w');
// hen.size  === 19
// hen.board[3][0] === BLACK
// hen.board[3][1] === BLACK
// hen.board[3][2] === WHITE

Hen.sgf2hen(sgfString, moveNumber = 0) -> string

Replay the main line of an SGF for moveNumber real B/W moves and return the resulting position as a HEN string.

  • moveNumber = 0 returns the initial setup position (after AB/AW/AE).
  • If the SGF has fewer than moveNumber real moves, the final position is returned.
  • Variations are ignored: at every fork the first child is followed.
  • Returns '' for invalid / empty SGF.

Hen.fromSgf(sgfString, moveNumber = 0) -> Hen | null

Like Hen.sgf2hen but returns a Hen instance instead of a string.

const hen = Hen.fromSgf(sgf, 10);
console.log(hen.lastMove, hen.turn, hen.size);

Hen.validate(henString) -> { ok, errors, warnings }

Validate a HEN string strictly against the formal grammar. Hen.parse() is lenient (it silently drops anything it cannot represent); Hen.validate() is the strict counterpart that surfaces every deviation.

const report = Hen.validate('_19r');
// {
//   ok: false,
//   errors: [ "Unsupported <stone> 'r' in '_19r' at offset 0 (only b/w supported)" ],
//   warnings: []
// }

const report2 = Hen.validate('.13x9_5b');
// {
//   ok: true,
//   errors: [],
//   warnings: [ "Non-square board .13x9 - using 13 as size, ignoring 9" ]
// }

Reported issues include:

Category Examples
Structural Unrecognized . part, unexpected character, empty row content
Unsupported Multi-colour stones r, g, l, y, p (only b/w here)
Out of range Row / column / run extends past the board
Numeric <number> not > 0, missing row number
Player order Fewer than 2 stones
Label chars Whitespace, ., _, - inside a label
Warnings Non-square <goban-size> (NxM with N ≠ M)

hen.toString() -> string

Re-serialise the position back to a HEN string (canonical form).

hen.toSgf() -> string

Render the position as an SGF string. All stones except the lastMove are emitted as setup (AB[]/AW[]); the lastMove (if any) is emitted as the sole playing node (;B[]/;W[]). Marks (TR, SQ, CR, MA) and labels (LB) round-trip when present.

Instance fields

All fields are exposed for reading (do not mutate them in place unless you know what you are doing):

Field Type
size number (default 19)
board number[][] (EMPTY=0, BLACK=1, WHITE=2)
koPoint { row, col } | null
lastMove { row, col, color, pass } | null
turn 'b' | 'w' | null
labels Array<{ row, col, letter }>
marks Array<{ row, col, mark }> (markCR/SQ/TR/MA)
numberedStones Array<{ row, col, number }>
playerOrder string[] | null

Coordinates are 0-based, row = 0 at the top, col = 0 at the left. HEN rows are 1-based from the bottom, so the conversion is internalRow = size - henRow.

HEN format reference

A HEN string is a concatenation of parts with no separator. Each part is introduced by a single character:

Prefix Meaning
.NxN Board size (defaults to 19x19 when omitted).
.<col><row> Ko point (e.g. .D4).
.<col><row>[bw] Last move with colour (e.g. .Q16w).
.p[bw] Pass move of the given colour.
.<col><row>-<val> Label (val is a string) or mark (CR/SQ/TR/MA).
.b / .w Whose turn it is to play.
_<row>… Row content (see below).
~<po> Player order for numbered stones (e.g. ~wb = white starts).

A row part is _<rowDigits> followed by an optional starting column letter (A-T, I skipped as per Go convention) and a sequence of stones. Stones are b or w; a digit N after a stone repeats it for N total stones of the same colour. ~N places a numbered stone (whose colour is derived from playerOrder) at the current column.

Example:

.9x9_8Gw_7Eb2w_6CbEwb_5Hw_4Gbw.H4w.b

decodes as a 9x9 position with several stones, last move W[H4], and black to play.

Running the tests

npm test

The suite uses node:test (built-in, no dependencies) and lives in test/.

Spec compliance

This library targets the HEN grammar defined in hen-spec/grammar.md. The table below summarises what is and is not implemented.

Grammar production Status
A - <goban-size> .NxN Parsed; rectangular .NxM accepted leniently (uses N, ignores M; Hen.validate emits a warning)
B - <goban-row> Full support
C - <ko> Full support
D - <last-move> (placement and pass) Full support for b/w
E - <turn> Full support for .b/.w
F - <label-annotation> Full support; invalid <label-char> reported by validate
G - <symbol-annotation> (CR/SQ/TR/MA) Full support
H - <player-order> Full support; validate enforces ≥ 2 stones
<column> A-Z (skip I) - up to 25 cols Supported (boards up to 25×25)
<stone> b|w|r|g|l|y|p b and w only; other colours are flagged by validate as unsupported
Numbered stones (~N inside a <goban-row>) Full support
Semantic constraint: <number> > 0 Enforced by validate; parse is lenient
Semantic constraint: rows/columns within range Enforced by validate; parse is lenient

If you need full multi-colour Go support, please open an issue.

Layout

src/
  index.js           public exports
  constants.js       EMPTY/BLACK/WHITE, HEN letters, SGF coord helpers
  sgf-parser.js      pure JS SGF parser (main-line only)
  hen-parser.js      HEN string → plain object
  hen-serializer.js  plain object → HEN string
  hen.js             Hen class gluing everything together
  validate.js        Hen.validate implementation (spec compliance checks)
test/
  fixtures.js        shared HEN/SGF samples
  hen.test.js        parser + serialiser coverage
  sgf.test.js        SGF ↔ HEN coverage
  edge.test.js       edge cases (variations, oversize N, etc.)
  spec.test.js       fixtures from the official hen-spec README
  validate.test.js   Hen.validate coverage (ok/error/warning)

About

JavaScript library for Hemme Notation (HEN)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors