Skip to content

Arimaa: Lightvector notation and click-based move entry - #484

Merged
Perlkonig merged 1 commit into
AbstractPlay:developfrom
samtcifihi:arimaa-lightvector-move-entry-2026-09-22
Sep 23, 2026
Merged

Perlkonig merged 1 commit into
AbstractPlay:developfrom
samtcifihi:arimaa-lightvector-move-entry-2026-09-22

Conversation

@samtcifihi

Copy link
Copy Markdown
Contributor

Enter moves by clicking pieces and where you want them to go. Should save clicks and be intuitive. I should write a more detailed entry on the Wiki, but mostly you should just be able to use the input system without understanding all the details of how it decides everything.

Moves are now recorded in Lightvector notation (Ee2e4, Rc2c3 Dd2x), which names pieces and outcomes rather than steps. Existing recorded games are unaffected: the old step-list format is still read, and replays trusted regardless of what the current validator would accept from a new submission. Just think of it as like Algebraic Notation from chess, and it becomes pretty intuitive.

Also fixed some preexistent bugs.

I've tested it on the developer playground and it seems perfect, the two things I really want to test on the dev server:

  1. Input delay. There was no noticeable input delay in the local playground, but I wouldn't expect that to mean much. I'm hoping it's efficient enough to not have noticeable input delay, but we'll see.
  2. Premoves don't care how you entered equivalent moves. There are six ways (if I'm counting right) for a piece to move to the opposite corner of a 3x3 square on an empty board. If I premove one of them, I want that premove to fire for any of them. I think that the normalization of move notation done here makes that possible, but I want to test it.

Move entry

handleClick builds up arrows (<piece><from><to>), "holds" (a piece confirmed to end where it stands: <piece><sq><sq>, drawn for round trips, detours, and forcing survival off a trap), and capture marks (<piece><sq>x) from clicks, and resolves them against every legal turn up to 4 steps deep. Resolution is strict by default (a token set must pick out exactly one turn) with a lenient fallback that breaks a tie by fewest own captures, then most enemy captures -- this is what lets two clicks usually be enough even though Arimaa turns can take up to 4 steps. A selection never blocks submission: move() drops a trailing selection rather than getting stuck, and complete is only ever 1 (auto-submit) when nothing about the turn had to be inferred.

A rejected drawing now says why instead of just "no legal move matches": the opponent's piece (also what a click lands on when the previous turn hasn't been completed yet), a piece boxed in or frozen, a rabbit sent backward, chained typed steps that read as one piece visiting two destinations, or arrows that each work alone but not together. Typed input still works: Lightvector notation directly (including Dir+ step tokens clicks never produce), or the old step-list format.

Compatibility

The recorded-move format changes, but old recorded games do not need to re-validate under the new rules to replay: a trusted move() call plays a turn (or a legacy step list) directly, without going back through validateMove. The version number is unchanged -- nothing about how a previously-recorded game is interpreted has changed, only what a new submission is asked to look like and how strictly it's checked.

Two moves the step validator incorrectly allowed are now rejected for new submissions: a push completed by a piece no stronger than the one it's pushing, and a pull that ends the pulled piece anywhere but the square the puller just vacated. Recorded games containing either (a handful of plies across the full corpus of recorded games) still replay unchanged, since replay never re-validates.

Performance

The step-sequence resolver (iterative deepening with an admissible heuristic bound) got measurably slower on some inputs once "holds" were added, since a piece that ends where it started can't be pruned by the step-count bound. A transposition memo (per depth, keyed on the board plus each moved piece's visited squares, off whenever a step token is present) plus a cheaper heuristic evaluation (cached witness lists, a distance table, early-outs once a bound already exceeds the remaining budget) brought the worst observed click, from a stress test over recorded games, down from ~0.9s to ~50-190ms, with no change to what the search finds (checked against 3,000+ random positions with pruning on and off).

Testing

test/games/arimaa.test.ts: 49 tests -- notation parsing, strict/lenient resolution, a randomized pruned-vs-unpruned equivalence check, arrow/hold/capture-mark entry (including forced arrows, detours, and trap-mark conversion), legacy-format compatibility (a submission the rules no longer allow is rejected, but the same string still replays trusted), 5 recorded games replayed move-by-move, and an independent exhaustive enumerator that checks every legal turn from several representative positions is reachable purely by clicks.

Full suite (npx mocha): 1502 passing, 1 pre-existing failure (storisende.storage.test.ts, a fixed 2000ms timeout on a large board) in a file this change does not touch; reproduces identically on a clean develop checkout. npm run lint (Weblate key check, game-names locale check, eslint): clean. tsc --noEmit on the changed files: clean.

Stress test: replayed all 449 games / 12,590 movement turns of a recorded-game corpus through both the old and new engines. Mean clicks per turn (old vs. new, Complete Move included): 7.39 -> 4.67. All but 3 turns (the illegal legacy plies above) are enterable purely by clicks under the new model. Also manually verified the click flows end-to-end by driving the actual playground/ app in headless Chromium (setup, single/multi-step turns, step-by-step re-selection, holds, an ambiguous drawing, a rotated board).

locales/en/apgames.json and locales/eo/apgames.json updated for the new and changed validation/instruction strings. Other locales are untouched -- they're machine-managed and will pick these up after merge.

Arimaa currently requires a player to type the raw step list of a turn
by hand (`Ee2e3, Ee3e4`, pushes and pulls spelled out with `(x...)`
captures). This replaces that with click/arrow-based move entry: click
a piece, then the square it should end the turn on (an enemy piece
too, for a push or pull), and the engine works out the steps. Every
legal turn is enterable this way; typing is never required.

Moves are now recorded in Lightvector notation (`Ee2e4`, `Rc2c3 Dd2x`),
which names pieces and outcomes rather than steps. Existing recorded
games are unaffected: the old step-list format is still read, and
replays trusted regardless of what the current validator would accept
from a new submission.

Move entry
----------
`handleClick` builds up arrows (`<piece><from><to>`), "holds" (a piece
confirmed to end where it stands: `<piece><sq><sq>`, drawn for round
trips, detours, and forcing survival off a trap), and capture marks
(`<piece><sq>x`) from clicks, and resolves them against every legal
turn up to 4 steps deep. Resolution is strict by default (a token set
must pick out exactly one turn) with a lenient fallback that breaks a
tie by fewest own captures, then most enemy captures -- this is what
lets two clicks usually be enough even though Arimaa turns can take up
to 4 steps. A selection never blocks submission: move() drops a
trailing selection rather than getting stuck, and `complete` is only
ever 1 (auto-submit) when nothing about the turn had to be inferred.

A rejected drawing now says why instead of just "no legal move
matches": the opponent's piece (also what a click lands on when the
previous turn hasn't been completed yet), a piece boxed in or frozen,
a rabbit sent backward, chained typed steps that read as one piece
visiting two destinations, or arrows that each work alone but not
together. Typed input still works: Lightvector notation directly
(including `Dir+` step tokens clicks never produce), or the old
step-list format.

Compatibility
-------------
The recorded-move format changes, but old recorded games do not need
to re-validate under the new rules to replay: a trusted move() call
plays a turn (or a legacy step list) directly, without going back
through validateMove. The version number is unchanged -- nothing about
how a previously-recorded game is interpreted has changed, only what a
new submission is asked to look like and how strictly it's checked.

Two moves the step validator incorrectly allowed are now rejected for
new submissions: a push completed by a piece no stronger than the one
it's pushing, and a pull that ends the pulled piece anywhere but the
square the puller just vacated. Recorded games containing either (a
handful of plies across the full corpus of recorded games) still
replay unchanged, since replay never re-validates.

Performance
-----------
The step-sequence resolver (iterative deepening with an admissible
heuristic bound) got measurably slower on some inputs once "holds"
were added, since a piece that ends where it started can't be pruned
by the step-count bound. A transposition memo (per depth, keyed on the
board plus each moved piece's visited squares, off whenever a step
token is present) plus a cheaper heuristic evaluation (cached witness
lists, a distance table, early-outs once a bound already exceeds the
remaining budget) brought the worst observed click, from a stress test
over recorded games, down from ~0.9s to ~50-190ms, with no change to
what the search finds (checked against 3,000+ random positions with
pruning on and off).

Testing
-------
test/games/arimaa.test.ts: 49 tests -- notation parsing, strict/lenient
resolution, a randomized pruned-vs-unpruned equivalence check,
arrow/hold/capture-mark entry (including forced arrows, detours, and
trap-mark conversion), legacy-format compatibility (a submission the
rules no longer allow is rejected, but the same string still replays
trusted), 5 recorded games replayed move-by-move, and an independent
exhaustive enumerator that checks every legal turn from several
representative positions is reachable purely by clicks.

Full suite (`npx mocha`): 1502 passing, 1 pre-existing failure
(storisende.storage.test.ts, a fixed 2000ms timeout on a large board)
in a file this change does not touch; reproduces identically on a
clean develop checkout. `npm run lint` (Weblate key check, game-names
locale check, eslint): clean. `tsc --noEmit` on the changed files:
clean.

Stress test: replayed all 449 games / 12,590 movement turns of a
recorded-game corpus through both the old and new engines. Mean clicks
per turn (old vs. new, Complete Move included): 7.39 -> 4.67. All but
3 turns (the illegal legacy plies above) are enterable purely by
clicks under the new model. Also manually verified the click flows
end-to-end by driving the actual playground/ app in headless Chromium
(setup, single/multi-step turns, step-by-step re-selection, holds, an
ambiguous drawing, a rotated board).

locales/en/apgames.json and locales/eo/apgames.json updated for the
new and changed validation/instruction strings. Other locales are
untouched -- they're machine-managed and will pick these up after
merge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YPW35p91rLsBRPSz63L1k
@Perlkonig

Copy link
Copy Markdown
Member

How does this handle games that are in progress? Will players see the notation change? And can you tell me what's different between current and lightvector format? It looks pretty similar to me. What are some examples of changes?

@Perlkonig

Copy link
Copy Markdown
Member

AI explained the differences :) I'll be sure to announce the change when it's finally released. Will merge and you can test on Dev.

@Perlkonig
Perlkonig merged commit 6c1876e into AbstractPlay:develop Sep 23, 2026
1 check passed
@samtcifihi

Copy link
Copy Markdown
Contributor Author

How does this handle games that are in progress? Will players see the notation change? And can you tell me what's different between current and lightvector format? It looks pretty similar to me. What are some examples of changes?

Already played moves will not change how they're recorded, but future moves will use the new Lightvector Notation. Here's a screenshot from the Developer Playground of the moveslist starting with a game state currently in progress on prod, and then entering 4 more plies with the new code:

image

The new notation should generally be shorter, but not so short that it's hard to read (it's guaranteed by design to not be ambiguous, and I forced a balance of brevity and redundancy that I think is good for humans (computers don't need completely redundant information, but judiciously chosen redundant information can make things much easier for humans to read))

The history of Lightvector notation is that it was created as a formally defined system which was backwards compatible with the arimaa.com gameroom notation, and gave the effect of allowing the notational shortcuts that people in the arimaa.com online chat tended to use (whether the resemblance to chess's algebraic notation is evidence that said notation is so awesome that another community independently decided something very much like it was best, or evidence that the arimaa community was familiar with chess notation, I leave as an exercise for historians, lol (it's probably both; we definitely have some chess players)).

@samtcifihi
samtcifihi deleted the arimaa-lightvector-move-entry-2026-09-22 branch September 26, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants