NAME

Game::Senet::Sticks - the four throwing sticks of Senet, as a pure function of a seed

VERSION

Version 0.02

SYNOPSIS

use Game::Senet::Sticks qw(throw_for light_of roll_of roll_for extra_throw);

my $seed   = 'any bytes at all';
my $sticks = throw_for($seed, 0);      # [1, 1, 1, 0], the same always
my $throw  = light_of($sticks);        # 3
my $roll   = roll_of($throw);          # 3

my $again  = roll_for($seed, 0);       # 3
print "throw again\n" if extra_throw($again);

DESCRIPTION

Senet is played with four flat sticks, each light on one face and dark on the other, each landing either way with equal chance. This module says how they fell, for a given seed and a given throw of the game, and what that is worth.

Every function here is pure. The same seed and the same throw number give the same sticks in every process, on every machine, at any time, whoever asks. There is no state, and nothing here reads a clock or a random number generator.

A throw is not a roll

A throw is the sticks as they fell, counted: how many landed light face up, 0 to 4. A roll is what that is worth in houses, 1 to 5.

For one to four light faces they are the same number. They part company at none: a throw of 0 is a roll of 5. roll_of is the one place the difference is decided, and every name in this distribution says which of the two it holds. A throw of 5 does not exist and neither does a roll of 0, so roll_of croaks on a 5 and extra_throw on a 0: confusing the two is an error and never a quiet wrong answer.

throw   0  1  2  3  4
roll    5  1  2  3  4
again   y  y  .  .  y

Another throw

A roll of 1, 4 or 5 gives the side another throw, whatever is done with it. extra_throw is asked of the ROLL, as the sources state it.

The seed is bytes

A seed is a string of bytes and is used as it is given: not decoded, not trimmed, not turned into hexadecimal. A string holding a character above 255 is not bytes, and is refused. The same bytes stored with the UTF-8 flag on or off give the same sticks.

A throw number belongs to the game, not to a player

Throws are numbered from 0 through the whole game, one a ply: a throw that is played, one that is forfeit, and an extra throw each take the next number. There is no opening throw, so the first throw of every game is number 0. Which side a throw belongs to is a fact of the game and not of the sticks, so a game picked up again at the same number gets the same throw.

Not the throws of another game

The throws of a seed here have nothing to do with what Game::RoyalUr makes of the same seed, and are not meant to. Two games sharing a seed do not share their throws. Changing how a seed becomes sticks would change every game ever recorded with one, which is why the sticks are pinned by a test.

FUNCTIONS

None is exported unless asked for. :all exports all five.

throw_for

my $sticks = throw_for($seed, $n);

Throw number $n of the game whose seed is $seed, as a reference to an array of four values, each 1 for a stick that landed light face up and 0 for one that landed dark. The sticks are returned and not only their count, so that a caller can draw them.

Croaks without a seed, on a seed that is not bytes, and on a throw number that is not a whole number from 0 up.

light_of

my $throw = light_of($sticks);

How many of the four sticks landed light face up: the throw.

roll_of

my $roll = roll_of($throw);

What a throw is worth in houses: the throw itself, except that a throw of 0 is worth 5. Croaks on anything but 0 to 4.

roll_for

my $roll = roll_for($seed, $n);

The three above in one call: the roll that throw $n is worth.

extra_throw

my $again = extra_throw($roll);

1 when a roll gives the side another throw: a roll of 1, 4 or 5. 0 for 2 and 3. Croaks on anything but 1 to 5.

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)