NAME
Game::Fanorona::Rules - a game of fanorona: a position, its history, and how it ends
VERSION
Version 0.03
SYNOPSIS
use Game::Fanorona::Rules ':all';
my $game = Game::Fanorona::Rules->new;
until ($game->over) {
my @steps = $game->steps;
$game->step($steps[0]);
$game->stop if $game->can_stop;
}
print $game->how, "\n"; # stones, no_moves, repetition, quiet
print $game->winner, "\n"; # 1 for White, 2 for Black, 0 for a draw
DESCRIPTION
A board with the moves that made it. Where Game::Fanorona::Engine will make any step it is given and plays on after the last stone is taken, a game refuses a step that is not legal and stops when it has ended.
A move is complete when the turn passes
After a step that takes nothing; after stop; and after a step that takes and leaves nothing that could follow. Only then is the move counted, the position written into the history, and the game judged. A step in the middle of a chain changes the board and nothing else.
How a game ends
Asked in this order once a move is complete, because one move can satisfy two.
- stones
-
A side has no stones left. The other side has won.
- no_moves
-
The side to move has stones and no legal move. It has lost. No traditional source says what happens here; this follows the one published analysis of the game that had to decide, and no game in thirty thousand played while this was being written reached it.
- repetition
-
The same position, with the same side to move, has occurred for the third time. A draw. Positions are compared between moves only: the middle of a chain is not a position. The sources give repetition as how a draw comes about and do not give the count; three is this distribution's.
- quiet
-
Sixty moves running, both sides counted, have taken nothing. A draw. This is not a traditional rule. It is here because a game between two players who cannot finish would otherwise not end, and the number is provisional.
- ply_cap
-
Three thousand moves. A draw, and a guard only: with the quiet limit above, a game cannot be that long.
Agreeing a draw and resigning are not here. They are something two people do, and the caller records them.
The limits
my $game = Game::Fanorona::Rules->new(variant => { quiet_limit => 40 });
quiet_limit, repeat and ply_cap. A quiet_limit or a repeat of 0 turns that draw off, which working out what a position is worth by force needs. The cap cannot be turned off. The limits are set when the game is made and do not change.
CONSTRUCTORS
new
my $game = Game::Fanorona::Rules->new;
my $game = Game::Fanorona::Rules->new(board => 'telo');
my $game = Game::Fanorona::Rules->new(position => $string, variant => \%limits);
A game from the set-up of a board, or from a position string. The starting position is judged at once, so a game begun with one side stoneless or stuck is over before a step is made.
Croaks on a board that is not one, a position that will not load, a position and a board together, or a limit that is not one.
clone
A second game with the same position, history and limits, sharing nothing with the first.
METHODS
steps
my @steps = $game->steps;
The steps the side to move may make, each the hash "A step" in Game::Fanorona::Engine describes. None once the game is over. In scalar context, how many.
can_stop
True when a chain is open and the game is not over: the mover may end the move here.
step
$game->step($step) or warn $game->why;
$game->step($from, $dir, $kind);
Plays a step and returns true, or refuses, changes nothing and returns false. After a refusal refused says whether the game was over or the step was illegal, and why gives the reason for an illegal one.
stop
Ends a move whose chain is open and returns true. False, with nothing changed, when no chain is open or the game is over.
refused
PLAY_OK after a step or stop that was made, PLAY_OVER after one refused because the game had ended, PLAY_ILLEGAL after one refused because it was not legal.
why
After an illegal step, the reason: one of the WHY_ values of "play_why" in Game::Fanorona::Engine. 0 otherwise.
undo
Takes back the last step or stop, with the counts and the outcome as they were. Returns false when there is nothing to take back.
undo_move
Takes back a whole move: the steps of the move in progress if a chain is open, and otherwise the last completed move. Returns false when there is nothing to take back.
depth
How many steps and stops undo could take back.
moves
my @moves = $game->moves;
The whole moves of the side to move, as Game::Fanorona::Move objects. None when the game is over or a chain is open. Takes the limit "moves" in Game::Fanorona::Engine takes, and croaks past it as that does.
play_move
$game->play_move($move) or warn $game->why;
Plays a whole move, each step through step and then a stop if the chain is still open. Returns true. If any step is refused, the steps already made are taken back and it returns false, with refused and why saying which refusal it was.
over
True once the game has ended.
outcome
ONGOING, BY_STONES, BY_NO_MOVES, DRAW_REPETITION, DRAW_QUIET or DRAW_PLY_CAP.
how
The same as a word: stones, no_moves, repetition, quiet, ply_cap; undef while the game goes on.
winner
The side that won, as the engine numbers them, 1 for White and 2 for Black; 0 for a draw or a game still going.
is_draw
True when the game ended without a winner.
ply
Whole moves completed.
quiet
How many of them, running up to now, took nothing.
repeats
How many times the present position has occurred, this time included.
key_at
my $key = $game->key_at($ply);
The key of the position after that many moves, or undef for a number the game has not reached.
history
The keys of every position between moves, the starting one first.
quiet_limit
repeat
ply_cap
The limits this game was made with.
position
The position string of the board as it stands.
side
The side to move.
at
count
key_hex
As Game::Fanorona::Engine's, of the game's board.
in_chain
True while a move is in progress.
chain_at
The point of the stone in the middle of its move, or -1.
search
my $found = $game->search(budget => 10_000, seed => 1);
The move the side to move should make, by looking ahead. undef when the game is over. The game is not changed.
{
steps => [ ... ], # the steps to make, each as steps() gives them
stop => 1, # a stop is needed after them to end the move
nodes => '9984', # positions looked at
depth => 4, # whole moves it looked ahead and finished
score => 100, # for the side to move
stopped => 1, # the budget ran out part way through a deeper look
}
From the middle of a chain it answers the rest of that move; steps is then empty when the best thing to do is stop.
- budget
-
How many positions it may look at: 10,000 unless given. It may be passed by a few, and a budget too small for one look at every move is passed by that much. It is a count and not a time: the same position, budget and seed give the same move on every machine.
- seed
-
Settles the choice among moves of equal score, and nothing else.
- depth
-
Whole moves to look ahead; 0, the default, for no limit but the budget.
- weights
-
A hash of the evaluation's weights that differ from the defaults.
A depth is counted in whole moves, so a chain of five steps is one. At the end of its depth the search does not score a position in which a stone can be taken: it plays the largest captures out, each side in turn, and scores what is left. That is a guess at how the exchange comes out, not a search of it.
A score of win_score less a little is a win that has been seen to its end.
evaluate
my $score = $game->evaluate;
my $score = $game->evaluate({ room => 3 });
The position scored for White, with no looking ahead: positive is good for White. A stone is worth a hundred. By default each empty point a stone could step to is worth two more, and each step it stands nearer the centre two more.
CLASS METHODS
weights
my $weights = Game::Fanorona::Rules->weights;
The evaluation's weights and their defaults, as a hash reference: stone, for each stone on the board; strong, for each stone on a point with diagonals; centre, for each step a stone stands nearer the middle; and room, for each empty point a stone could step to. strong is 0 by default: it helped by itself and not beside the other two.
win_score
The score of a win. A win some steps away scores that many less.
live
How many games exist that have not been destroyed.
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)