NAME
Game::Senet - Senet, the Egyptian game of passing
VERSION
Version 0.02
SYNOPSIS
use Game::Senet;
my $game = Game::Senet->new(seed => 'any bytes at all');
while (!$game->is_over) {
printf "%s threw %s, worth %d\n", $game->side, join('', @{ $game->sticks }), $game->roll;
my @moves = $game->legal;
$game->play($moves[0]);
}
print $game->result->winner, " won\n";
my $skyruk = Game::Senet->new(seed => 'other bytes', rules => 'skyruk');
$skyruk->play('10-12') or print $skyruk->error->message, "\n";
DESCRIPTION
A game of Senet: thirty houses in three rows of ten, five pieces a side, four throwing sticks, and one S-shaped track both sides share from the first throw. Light stands on the even houses of the first row and dark on the odd, and light throws first.
The sticks are thrown from a seed: the same seed gives the same game of throws every time, and a game can be recorded and replayed exactly. Nothing here reads a clock or a random number generator.
The rules are after Timothy Kendall's reconstruction of 1978, as modern sets play it (kendall, the default), or the reconstruction published with an open-source implementation of the game (skyruk). Nobody knows the original rules. See Game::Senet::Variant.
It is the engine behind the Senet at https://peer2peergames.com.
A throw is not a roll
The four sticks land light or dark face up. The throw is how many show light, 0 to 4; the roll is what that is worth in houses, 1 to 5: a throw of none is a roll of 5. sticks gives the sticks, throw the count, roll the houses.
A turn is one throw or several
A roll of 1, 4 or 5 gives the side another throw, whatever was done with it, even nothing. A 2 or 3 passes the sticks. Under skyruk a piece that reaches house 26 is thrown for again, alone. So turns do not alternate, and side says whose throw it is.
The game moves on by itself when it must
When a throw allows no move at all, the game records a forfeit and throws again for whoever is next. A caller never sees a throw with nothing to play: between calls the game is either over or legal holds at least one move. A throw with one legal move waits for play like any other.
The board
The houses are numbered along the track, which runs as an S: left to right along the top row, right to left along the middle, left to right along the bottom, and off the board after house 30.
1 2 3 4 5 6 7 8 9 10 >--.
|
20 19 18 17 16 15 14 13 12 11 <--'
| +
'-> 21 22 23 24 25 26 27 28 29 30 > off
X ~ III II I
The marked houses, with the sign the terminal draws for each:
15 + the House of Rebirth, where a piece from the water comes back
26 X the House of Happiness, which every piece must stand on
27 ~ the House of Water
28 III the House of Three Truths, left on a roll of exactly 3
29 II the House of Re-Atoum, left on a roll of exactly 2
30 I the House of Horus
The sticks
light faces up (the throw) 0 1 2 3 4
houses moved (the roll) 5 1 2 3 4
chance, in sixteenths 1 4 6 4 1
throws again yes yes no no yes
Moving
A piece moves forward by the roll. It may not land on a piece of its own side. Landing on a piece of the other side is a swap: the two pieces exchange houses. Wikipedia's account of Kendall's rules sends the piece landed on backward to the first empty house instead; the sets played here swap, as the other accounts do. A piece is protected, and cannot be swapped, when it stands in a run of its own side, and a run long enough is a blockade no piece of the other side may pass. When no piece can move forward, a piece must move backward if one can. No piece passes house 26 without standing on it.
The two rule sets
kendall skyruk
protected every piece of a run the first and the last
of two or more of a run of two or more
blockade three in a row two in a row
reaching 26 on the exact roll from 25, on any roll
on reaching 26 nothing more one more throw, for that
piece alone
27, the water back to 15 at once stood on, and left by a
throw: 1 to 3 back to 15,
4 off, 5 stays
30 off on a roll of 1 off on any roll
Both start with five pieces a side, and pieces => 7 gives seven. Game::Senet::Variant has every field.
kendall is after Timothy Kendall's reconstruction (1978), as modern sets play it. Kendall's own text is not quoted anywhere these rules were taken from, and the accounts of it differ; where they do, the set follows the one complete account. skyruk is a reconstruction published with an open-source implementation. A third account, called masters after the site that gives it, was not chosen as the second set, because it leaves out what happens backward, in a run, and beyond house 26.
The words
A piece stands on a house. The two sides are light and dark. The sticks are thrown; the throw is how many fell light face up and the roll what it is worth. A swap exchanges two pieces; a run is houses held next to each other by one side; a piece leaving the board bears off and is home. A throw that allows no move is a forfeit.
Sources
The rules were taken on 17 Oct 2026 from: Wikipedia's article "Senet" (revision 1375538342); the rules page of Masters Traditional Games; Catherine Soubeyrand's page at The Game Cabinet, which gives Kendall's rules as Lhote summarised them (1994); the README of the gym-senet project, which gives both sets as a specification; and a Tabletop Simulator workshop item based on Kendall's rules. Nobody knows how Senet was played when it was played.
CONSTRUCTOR
new
my $game = Game::Senet->new(seed => $bytes, %options);
seed is any non-empty string of bytes. The options:
- rules
-
A rule set: a name (
kendallorskyruk), a hash of fields, or a Game::Senet::Variant.kendallwhen not given. - first
-
light(the default) ordark: which side throws first. No source has dark first; it is there for a caller who wants it. - light
-
p1(the default) orp2: the seat that plays light. See "side_of". - position
-
A position string to start from instead of the start, with as many pieces a side as the rules. Its side to move is the one who throws first unless
firstis given. - script
-
The throws themselves, in place of a seed: a reference to an array of rolls, or of
{ roll, sticks }. For replaying a record with no seed.
A key that is not one of these is ignored in silence: rulez plays kendall. A game with no seed and no script croaks.
METHODS
side
light or dark: whose throw it is. undef once the game is over.
sticks
The four sticks in hand, each 1 for a light face, as an array reference.
throw
How many sticks show light, 0 to 4.
roll
What the throw is worth in houses, 1 to 5.
legal
The moves the throw allows, as Game::Senet::Moves. Never empty while the game goes on.
play
$game->play('12-15') or die $game->error->message;
$game->play($move);
Plays a move: a wire string (see Game::Senet::Notation) or a move legal returned. A move is played if and only if legal holds it. Returns the move, or undef with error saying why not, having changed nothing.
play_or_die
As play, croaking with the reason.
error
The Game::Senet::Error of the last refusal, or undef.
undo
Takes back the last move, and every forfeit the game made after it, to the throw the move was made with. Or takes back a resignation. 0 when there is nothing to take back.
resign
$game->resign('dark');
Ends the game for a side, the side to move by default.
is_over
result
A Game::Senet::Result once the game is over, undef before.
turn
my $turn = $game->turn; # { side, segment, bound, extra }
Whose turn it is, which throw of the turn this is (1 for the first), the house whose piece alone this throw may move or undef, and whether this throw gives another.
side_of
my $side = $game->side_of('p2');
The side a seat plays.
seat_of
my $seat = $game->seat_of('light');
The seat that plays a side.
variant
The rule set, a Game::Senet::Variant.
first
The side that threw first.
seed
The seed, as given.
throws
How many throws have been made, the one in hand included.
ply
Moves and forfeits made.
position
The position string.
key
The position's key: sixteen hex characters.
home
my $n = $game->home('light');
at
my $who = $game->at(15); # 'light', 'dark' or undef
protected
in_blockade
print "safe from a swap\n" if $game->protected(12);
print "nobody passes it\n" if $game->in_blockade(12);
Whether the piece on a house cannot be swapped, because it stands in a run of its own side the rules protect, and whether it is part of a run the other side may not pass. Each is 0 for an empty house, so a page can draw both and know no rule. A house that is not 1 to 30 croaks.
rules_revision
my $rev = Game::Senet->rules_revision; # 1
A number that goes up when a release changes what a game does: a different move allowed, refused or made, from the same seed. A game stored under one revision is a different game under another. A release that changes nothing a game does keeps the number, whatever its version.
chances
Each roll the sticks can make, with its throw, how many of the sixteen ways four sticks fall make it, and whether it gives another throw.
log
Every move and forfeit, in order, each a hash: ply, side, throw (its number), roll, sticks, move (the wire form, or undef for a forfeit), and again, whether the same side threw next.
forfeits_since
my @forfeits = $game->forfeits_since($ply);
The forfeits at or after a ply, from the log, so that a caller can say that a side threw and could not move.
to_record
The game as a record (see Game::Senet::Notation).
to_text
The same.
from_text
my $game = Game::Senet->from_text($text);
The game a record describes, or a croak saying where it disagreed.
replay
my ($game, $error) = Game::Senet->replay($record_or_text);
my ($game, $error) = Game::Senet->replay($record, rules => 'skyruk');
Plays a record again through play, throw by throw. Answers the game, or undef and an error naming the ply. With a seed every throw is thrown again and checked; a record the rules no longer accept is refused where they part.
AUTHOR
LNATION <email@lnation.org>
BUGS
Please report any bugs or feature requests to bug-game-senet at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Game-Senet. 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::Senet
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)