NAME
Game::RoyalUr - the Royal Game of Ur
VERSION
Version 0.01
SYNOPSIS
use Game::RoyalUr;
my $game = Game::RoyalUr->new(seed => 'any bytes at all');
until ($game->is_over) {
my @moves = $game->legal; # never empty while the game is on
printf "%s rolled %d\n", $game->side, $game->roll;
$game->play($moves[0]) or die $game->error->message;
}
my $result = $game->result;
print $result->winner, ' wins by ', $result->how, "\n";
print $game->to_record; # the whole game, as text
DESCRIPTION
The Royal Game of Ur is a race for two players on a board of twenty squares. Each side has seven pieces, throws a handful of two-sided dice, and moves one piece that many squares along a route that crosses the other side's. Landing on an enemy piece sends it back to the start; landing on one of the five rosettes earns another roll; the first side to bring every piece home wins.
The board is about 4,600 years old. The rules are not known. Every set of rules is a modern reconstruction, and this distribution plays two of them, by name: finkel, proposed by Irving Finkel of the British Museum, and masters, proposed by James Masters. Game::RoyalUr::Variant says how they differ.
It is the engine behind the Royal Game of Ur at https://peer2peergames.com.
The dice come from a seed
A game is made with a seed, some bytes, and every throw of its dice follows from them. Two games with the same seed and the same moves are the same game, on any machine and at any time. Nothing here reads a clock or a random number generator.
A seed decides the dice and so it decides the game: whoever knows it knows every roll to come. seed hands it back to whoever asks. A program that plays for stakes keeps it away from its players.
A turn is a roll and then one move
Between calls, a game that is not over always has a side to move, a roll already thrown for it, and at least one move that roll allows. roll is that roll, legal is those moves, and play makes one of them.
A caller never has to pass. When a roll allows no move, a throw with nothing marked or a roll with nowhere to go, the turn is lost by the game itself and the next side throws, and so on until somebody has a choice. Those lost turns are in the log, and forfeits_since hands them over so that they can be shown and not silently skipped.
A side with exactly one legal move still makes it. The game does not play for a side that has a move.
Turns do not alternate
A piece that lands on a rosette earns its side another roll. Ask side after every move.
Squares and moves
The board is three rows of eight squares with four missing:
3 a3 b3 c3 d3 . . g3 h3 dark's row
2 a2 b2 c2 d2 e2 f2 g2 h2
1 a1 b1 c1 d1 . . g1 h1 light's row
A move is written as two places with a dash between them: a2-d2, hand-b1 for a piece entering the board, g1-home for one leaving it. See Game::RoyalUr::Notation.
THE GAME
The two rule sets
finkel masters
pieces a side 7 7
dice 4 3
a throw of nothing moves nothing moves four
the route short, 14 steps long, 16 steps
squares shared the 8 of row 2 12
a rosette gives another roll another roll
a piece on a rosette cannot be captured can be
leaving the board on the exact roll on the exact roll
Under both, a side that can move must, a capture is allowed and never compulsory, a piece passes over any piece in its way, and two pieces never share a square.
The routes
A piece enters on the fourth square of its own row and walks toward the corner. These are light's steps; dark's are the same with rows 1 and 3 exchanged. A dot is a square the route does not visit.
finkel, the short route
3 . . . . . .
2 5 6 7 8 9 10 11 12
1 4 3 2 1 14 13
a b c d e f g h
masters, the long route
3 . . . . 12 13
2 5 6 7 8 9 10 11 14
1 4 3 2 1 16 15
a b c d e f g h
The rosettes are a1, g1, a3, g3 and d2: steps 4, 8 and 14 of the short route, and every fourth step of the long one.
On the short route the two sides meet only in row 2. On the long route each side's last five steps run round the far end of the board through the other side's row, so after its first four squares a piece is never out of reach.
Words
- light, dark
-
The two sides. Light's row is row 1 and dark's is row 3.
- hand
-
A side's pieces that have not yet entered the board.
- home
-
A side's pieces that have left the board at the end of their route.
- step
-
A place on a side's route, counted from 1. The same square can be a different step for each side.
- throw, roll
-
A throw is the dice as they fell. A roll is what that is worth in steps. They differ only where a throw with nothing marked is worth four.
- rosette
-
One of the five marked squares.
- capture
-
Landing on an enemy piece, which sends it back to its owner's hand.
- forfeit
-
A turn lost to the roll: a roll of nothing, or a roll that allows no move.
Where the rules come from
Three texts, read on 9 October 2026:
Wikipedia, "Royal Game of Ur", revision 1378861936.
Masters Traditional Games, "The Rules / Instructions of The Royal Game of Ur", by James Masters, which sets out his route and his rules beside R. C. Bell's and Irving Finkel's.
RoyalUr.net, "Rules of the Royal Game of Ur", by Padraig Lamont.
They do not agree about everything, and say nothing about some things. Two readings are this distribution's own and are worth knowing. Where a source is silent on whether a piece may pass over another, it may. And James Masters' own page does not say whether his rosettes protect a piece; the other two sources say they do not, and that is what masters plays.
The same sources disagree about where Irving Finkel's rules come from: one says he translated them from a cuneiform tablet, and another says the tablet describes a more complicated game and that his rules were made to play well on the boards that survive. Either way they are a reconstruction, made in the twentieth century, of a game whose own rules are lost.
The short route under four dice has 275,827,872 positions, and has been solved: RoyalUr.net publish the chance of winning from every one. This distribution's board counts the same number, and on every position checked (all of the two-piece game, and twenty thousand of the full one) its rules agree with those published values to the last digit they are stored to. Under perfect play the side that moves first wins 51.54 games in a hundred.
METHODS
new
my $game = Game::RoyalUr->new(seed => $bytes);
my $game = Game::RoyalUr->new(seed => $bytes, rules => 'masters', first => 'dark');
seed-
Any non-empty string of bytes. Required, unless
scriptis given. rules-
'finkel'or'masters', a Game::RoyalUr::Variant, or a hash reference of the fields that differ fromfinkel.'finkel'when it is left out. first-
'light'or'dark': the side that moves first. When it is left out the two sides throw for it, with the seed's own dice: the side with more dice marked moves first, and a tie is thrown again. Those throws are the game's first, andopeningreturns them. position-
A position to start from, as a string. The side to move is the position's unless
firstnames another, and there is no opening throw. script-
In place of a seed: the rolls themselves, in order, as a reference to an array of
{ roll => ..., faces => ... }, the faces optional. This is a game played with real dice. It needsfirstorposition, since there are no dice to throw for it, and when the rolls run out it has no roll and no moves until it is made again with more.
Croaks with neither a seed nor a script, and on rules, a side or a position it will not take.
side
'light' or 'dark': the side to move. undef once the game is over.
roll
What the side to move has rolled, 1 to 4. undef once the game is over. It is the same however often it is asked: the dice are thrown once a turn.
throw
The dice behind that roll, as a reference to an array with a 1 for each marked die and a 0 for each unmarked one, so that they can be drawn.
legal
my @moves = $game->legal;
The moves the roll allows, as Game::RoyalUr::Move objects. Never empty while the game is on; empty once it is over. In scalar context, how many.
play
my $move = $game->play('a2-d2') or warn $game->error->message;
my $move = $game->play($moves[0]);
Makes a move, given as text or as one of the objects legal returned, and returns it. The next roll is thrown, and any turns it loses are lost, before play returns.
A move that is not legal returns undef, changes nothing, not the board, not the roll and not the dice, and leaves the reason in error.
play_or_die
As play, and croaks with the reason where play would have returned undef.
error
The Game::RoyalUr::Error from the last play that was refused, and undef after one that was not.
undo
Takes back the last move, and with it any turns that were lost after it, so that the game is as it was when that move was chosen: the same side, the same roll. True when something was taken back. A resignation is taken back first.
resign
$game->resign; # the side to move
$game->resign('dark');
Ends the game in favour of the other side. True when done, false when the game was already over.
is_over
True once the game has been won, drawn or resigned.
result
A Game::RoyalUr::Result once the game is over, and undef until then.
position
The position as a string. See "to_string" in Game::RoyalUr::Engine.
key
Twelve hexadecimal characters that stand for the position.
at
my $who = $game->at('d2'); # 'light', 'dark' or undef
What stands on a square. Croaks on a name that is not a square.
hand
my $waiting = $game->hand('light');
How many of a side's pieces have not yet entered the board.
home
my $finished = $game->home('dark');
How many of a side's pieces have come home.
ply
How many moves and lost turns have been made.
rolls
How many times the dice have been thrown, the opening throws and the throw for the turn in progress included.
first
'light' or 'dark': the side that moved first.
opening
The throws that decided who moves first, in order, light's and then dark's and so on, each as throw returns one. Empty when the game was told.
variant
The game's Game::RoyalUr::Variant.
seed
The seed the game was made with.
chances
my @chances = $game->chances;
What the game's dice can roll and how often, as "chances" in Game::RoyalUr::Engine returns it.
log
Everything that has happened, in order, as a reference to an array of hash references, one a ply:
{ ply => 3, side => 'light', throw => 5, roll => 4, faces => '1111',
move => 'a2-e2', captures => 0, rosette => 0, home => 0 }
throw is the number of the throw, counted through the whole game. A turn lost to the roll has move => undef. The log is a copy.
forfeits_since
my @lost = $game->forfeits_since($ply);
The log's entries for turns lost to the roll at or after a ply, so that a caller who last looked at ply $ply can say what happened in between.
to_record
The whole game as text, in the form Game::RoyalUr::Notation describes: the rules, who moved first, the seed, and every turn with its dice.
replay
my ($game, $error) = Game::RoyalUr->replay($text);
my ($game, $error) = Game::RoyalUr->replay($record, rules => 'masters');
A game rebuilt from a record, as text or as the structure "parse_record" in Game::RoyalUr::Notation returns, and undef; or undef and a Game::RoyalUr::Error whose detail names the ply at which the record stopped describing a game.
Every move in the record is played, so a record the rules do not allow is refused at the move that breaks them. With a seed, every roll is thrown again and must be the roll the record says. Without one the record's rolls are taken as written, which is how a game played with real dice is replayed; such a game can be read but not played on, because nobody can say what would have been rolled next.
rules replays the record under another rule set than its own.
SEE ALSO
Game::RoyalUr::Variant, the rule sets. Game::RoyalUr::Notation, moves and records as text. Game::RoyalUr::Rules, a game without its dice. Game::RoyalUr::Engine, the board. Game::RoyalUr::Dice, the dice.
AUTHOR
LNATION <email@lnation.org>
BUGS
Please report any bugs or feature requests to bug-game-royalur at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Game-RoyalUr. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Game::RoyalUr
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
Search CPAN
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)