A small, dependency-free JavaScript / Node.js library for converting between HEN and SGF formats.
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.
You can install from GitHub:
npm install github:hemme/hen-jsOr use it directly from source (Node >= 18, ESM):
git clone https://github.com/hemme/hen-js.git
cd hen-jsimport { 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();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] === WHITEReplay the main line of an SGF for moveNumber real B/W moves and
return the resulting position as a HEN string.
moveNumber = 0returns the initial setup position (afterAB/AW/AE).- If the SGF has fewer than
moveNumberreal moves, the final position is returned. - Variations are ignored: at every fork the first child is followed.
- Returns
''for invalid / empty SGF.
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);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) |
Re-serialise the position back to a HEN string (canonical form).
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.
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 }> (mark ∈ CR/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.
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.
npm testThe suite uses node:test (built-in, no dependencies) and lives in test/.
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.
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)