NAME
Game::RoyalUr::Notation - moves, positions and game records of the Royal Game of Ur, as text
VERSION
Version 0.01
SYNOPSIS
use Game::RoyalUr::Notation qw(parse_move format_display parse_record format_record);
my ($move, $error) = parse_move('hand-b1'); # { from => 'hand', to => 'b1' }
print format_display($move_object), "\n"; # 3: a2-d2x*
my ($record, $problem) = parse_record($text);
die "line $problem->{line}: $problem->{error}\n" unless $record;
print format_record($record);
DESCRIPTION
Functions that read and write a move, a turn and a whole game as text. There is no object here and nothing is kept between calls.
This module needs no compiled code. It loads, and does everything below, on a machine where the rest of the distribution could not be built.
A move, three ways
hand-b1 the wire form
3: a2-d2x* the display form
0: - a turn lost to the roll
The wire form names two places and nothing else. A place is a square, a file letter a to h and a row digit 1 to 3, or the word hand for a piece entering the board, or the word home for a piece leaving it. It carries no roll: a move cannot tell a game what was rolled.
The display form leads with the roll, then the wire form, then x if the move captures and * if it lands on a rosette, in that order.
A forfeit is the roll and a dash.
Everything is written in lower case. The two words and the file letters are read in either case, and nothing else is forgiven: no spaces, no other separator, and no x or * on a wire move.
A wire move is not checked against a board. b1-h3 is read without complaint. Whether it is a move in some position is a question for a game.
A game record
[rules finkel]
[first light]
[seed 5f1c9a]
1. l 3: hand-b1
2. d 0: -
3. l 4 1111: b1-c2
[result light resign]
A header and then one turn a line.
[rules ...]-
finkelormasters, or the five fields of a rule set spelled out in order:route=long dice=3 zero_rolls=4 safe_rosettes=1 pieces=7. [first ...]-
lightordark: the side that moves first. [seed ...]-
Optional. The seed's bytes, in hexadecimal. With a seed, every roll in the record is checked against the dice of that seed, and a record that disagrees is refused. Without one the rolls are taken as written, which is how a game played with real dice is recorded.
[opening N]-
Optional, and only with a seed: how many throws were spent deciding who moves first, so that the game's own throws are numbered from there.
- a turn
-
Its number, counted from 1; the side,
lord; the roll; optionally the dice as they fell, a1for a marked die and a0for an unmarked one; a colon; and the move in wire form, or a dash for a turn lost.The side is on every line because turns do not alternate, and it is checked and not trusted: after a move onto a rosette the same side moves again, and after anything else the other side does.
[result ...]-
Optional, and last.
light homeordark homefor a game won by bringing the last piece home,light resignordark resignnaming the winner of a game that was resigned, anddraw ply_cap.
A record ends with a newline, and a record that has been read and written again is the same text, byte for byte.
A record is checked for its own consistency: its numbering, its sides, its dice. It is not played. Whether each move was legal is a question for a game, which is asked by replaying it.
FUNCTIONS
None is exported unless asked for. :all exports everything.
square_ok
square_ok('d2') # true
square_ok('e1') # false: there is no such square
True for the name of one of the twenty squares, in lower case.
parse_move
my ($move, $error) = parse_move($text);
A reference to { from => ..., to => ... } in lower case and undef, or undef and one of:
empty-
No text.
shape-
Not two places with one dash between them.
place-
One of the two is not a square,
handorhome. from_home-
A piece that has come home does not move again.
to_hand-
Nothing moves to the hand.
format_move
my $wire = format_move($move);
The wire form of a move: a Game::RoyalUr::Move, or a hash reference with from and to.
format_display
my $shown = format_display($move);
my $shown = format_display($move, $roll);
The display form. The roll, and whether the move captures or lands on a rosette, are read from the move; a roll given as the second argument is used instead of the move's own.
format_forfeit
my $shown = format_forfeit($roll); # '0: -'
A turn lost to that roll, in the display form.
validate_position
my $code = validate_position($string);
POS_OK for a position string in the form "to_string" in Game::RoyalUr::Engine writes, or the reason it is not one. It agrees with "of_string" in Game::RoyalUr::Engine on every string, and needs no board to say so.
parse_record
my ($record, $problem) = parse_record($text);
A record and undef, or undef and a reference to { line => ..., error => ... } naming the first line that is wrong, counted from 1, and why.
A record is a hash reference:
{
rules => 'finkel', # or a hash of the five fields
first => 'light',
seed => '5f1c9a', # when the record has one
opening => 2, # when the record has one
turns => [
{ side => 'light', roll => 3, move => 'hand-b1' },
{ side => 'dark', roll => 0, move => undef },
{ side => 'light', roll => 4, faces => '1111', move => 'b1-c2' },
],
result => { winner => 'light', how => 'resign' }, # when it has one
}
format_record
my $text = format_record($record);
The text of a record in that shape.
CONSTANTS
POS_OK,POS_NULL,POS_ROWS,POS_WIDTH,POS_LETTER,POS_GAP,POS_X,POS_SIDE,POS_COUNT,POS_FIELD,POS_LONG-
What
validate_positionanswers, with the meanings and the values Game::RoyalUr::Engine gives the same names.
AUTHOR
LNATION <email@lnation.org>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)