NAME
Game::Fanorona - the board game of Madagascar
VERSION
Version 0.03
SYNOPSIS
use Game::Fanorona;
my $game = Game::Fanorona->new;
$game->play('d3-e3A') or die $game->error->message;
print $game->turn, "\n"; # black
print join(' ', $game->legal_strings), "\n"; # c3-d3A c3-d3W d4-d3A f4-f3A
# or a step at a time, which is how to offer a person a move
for my $step ($game->steps) {
print "$step->{string} takes @{ $step->{takes} }\n";
}
$game->step('c3-d3A');
$game->stop if $game->can_stop;
if (my $result = $game->result) {
print $result->is_draw ? 'a draw' : $result->winner . ' won', "\n";
}
DESCRIPTION
Fanorona is played on forty-five points joined by lines, nine across and five up, with twenty-two stones a side and the centre empty. A stone steps along a line to the next point. If that brings it up against an enemy stone straight ahead, or takes it away from one straight behind, that stone and every enemy stone in an unbroken line beyond it are taken. Taking is compulsory. A stone that has taken may go on taking in the same move, never twice running in the same direction and never back to a point it has stood on, and may stop when it likes. The side that takes every stone wins.
This class is the game as a person or a program holds it: it speaks in point names and move strings, refuses what is not legal without dying, knows when the game has ended, keeps the moves so the game can be played again, and handles the two things that are not moves, resigning and agreeing a draw.
Under it are Game::Fanorona::Rules, Game::Fanorona::Engine and Game::Fanorona::Notation, which say exactly what the rules are, which of them are traditional and which are this distribution's own, and how a move is written.
It is the engine behind the Fanorona at https://peer2peergames.com.
Sides and points
The sides are white and black. White moves first and starts on the two ranks nearest it. A point is a file letter a to i and a rank digit 1 to 5.
A move and a step
A step is one stone going to one adjacent point: b2-c2A. A move is everything one side does before the other plays: b2-c2A-c3A. The letter after a point says the step that arrived there took, A by approach and W by withdrawal.
play makes a whole move. step and stop make it a step at a time. A game can be driven either way, or both.
Reading a move of one step, a step that takes may be written without its letter where only one way of taking is possible there. Where both are, the letter is needed, and the step is refused as choose_capture. In a move of two steps or more every step takes and every step must carry its letter. Moves this class writes always carry the letter.
Offering a person a move
Do not list the moves. There are usually about ten and can be thousands, because a stone that could take four times running makes four moves, one for each place it might stop. Offer steps instead: the handful of single steps that may come next. When one is taken, offer steps again, and stop where can_stop says so. starts is the stones that can begin a move, and continuations answers the same question for a move that has not been made yet.
Refusals
Nothing here dies because a move was not legal. play, step, stop, preview, continuations, resign and the three draw calls return nothing when they refuse, and leave an Game::Fanorona::Error in error. A call that succeeds clears it.
CONSTRUCTORS
new
my $game = Game::Fanorona->new;
my $game = Game::Fanorona->new(variant => 'telo');
my $game = Game::Fanorona->new(variant => { board => 'sivy', quiet_limit => 40 });
my $game = Game::Fanorona->new(position => '9/9/2bw1b3/9/9 w');
my $game = Game::Fanorona->new(script => [ 'd3-e3A', 'c3-d3W' ]);
- variant
-
A name, the one-line form "as_string" in Game::Fanorona::Variant writes, a hash of fields for "custom" in Game::Fanorona::Variant, or a variant object. The nine by five from its set-up when absent.
- position
-
A position to begin from in place of the variant's own. It must be a position of a board that size.
- script
-
Moves to play at once. Croaks if one is refused.
- first
-
May be given as
white, which is the only side that ever moves first. Anything else croaks.
Croaks on a variant that is not one or a position that will not load.
An argument name it does not know is not reliably noticed. If it comes first, every argument is read by position instead of by name and the call usually croaks, complaining about something else. If it comes after a name that is known, it is dropped in silence. Check the spelling of variant, position, script and first.
replay
my ($game, $error, $n) = Game::Fanorona->replay(\@moves, variant => 'telo');
A game with the moves played, through the same door play uses. If one is refused the game is returned as far as it got, with the refusal and the number of the move, counting from 1. Takes the arguments new takes.
from_text
my ($game, $why, $line) = Game::Fanorona->from_text($text);
A game from the text as_text writes. On failure the game is undef, and $why is a word: one of "parse_game" in Game::Fanorona::Notation's if the text is malformed, or an Game::Fanorona::Error code if a move in it was refused; $line is the line at fault.
The game comes back as it was: a move half made is made that far, a standing offer of a draw stands, and a game that ended by resignation or agreement has ended. Two more words may be $why: bad_chain, for a chain line that is a whole move, and bad_result, for a result line the game does not bear out: a resignation or an agreement in a game already over, or an ending on the board the moves do not reach.
METHODS
play
my $move = $game->play('b2-c2A-c3A') or warn $game->error->message;
Plays a whole move, given as a string or as a Game::Fanorona::Move, and returns it as a Game::Fanorona::Move. If the move ends where its stone could have gone on, that is a stop and is part of the move.
If any step of it is refused, nothing is played.
step
my $step = $game->step('b2-c2A') or warn $game->error->message;
Plays one step and returns it described, as steps describes one. The move is complete if the step took nothing, or took and left nothing that could follow; otherwise the same side is still to move and can_stop is true.
stop
Ends a move in progress. True, or nothing with nothing_to_stop in error.
steps
for my $step ($game->steps) { ... }
The steps that may be made next, each a hash:
{
from => 'b2',
to => 'c2',
how => 'approach', # or 'withdrawal', or 'paika'
takes => [ 'd2', 'e2' ], # nearest first; empty for a paika
string => 'b2-c2A', # what step() takes
}
None once the game is over. A step that could take two ways is listed twice.
can_stop
True while a move is in progress and the game is not over.
starts
my @points = $game->starts;
The points whose stones have a step to make.
continuations
my $next = $game->continuations('b2-c2A');
What could follow a move so far, without making it:
{
steps => [ ... ], # as steps() gives them
stop => 1, # the move so far could end here by choice
complete => 0, # the move so far ends here of itself
}
With no argument it describes the game as it stands. A move so far is a legal move exactly when complete or stop is true. Nothing, with error set, when the move so far cannot be made.
legal
my @moves = $game->legal;
my @moves = $game->legal($limit);
The whole legal moves, as Game::Fanorona::Move objects. None once the game is over or while a move is in progress. Croaks when there are more than the limit, which is 100,000 unless given: see "Offering a person a move".
legal_strings
The same as strings, sorted.
preview
my $position = $game->preview('b2-c2A-c3A');
The position a whole move would leave, without playing it. Nothing, with error set, for a move that would be refused.
turn
white or black, the side to move; undef once the game is over.
over
True once the game has ended, however it ended.
result
An Game::Fanorona::Result once the game is over, and undef before.
winner
white, black, or undef for a draw or a game still going.
error
The last refusal, an Game::Fanorona::Error, or undef if the last call that could refuse did not.
resign
$game->resign('black');
Ends the game in favour of the other side. Either side may resign at any time, its own move or not.
offer_draw
$game->offer_draw('white');
Puts a draw on the table. Either side may, at any time, while no offer is standing. An offer lapses when the side that made it completes a move.
accept_draw
$game->accept_draw('black');
Ends the game as a draw by agreement. Only the side the offer was made to may answer it.
decline_draw
Takes the offer off the table.
draw_offered_by
The side whose offer is standing, or undef.
search
my $found = $game->search(budget => 10_000);
print "$found->{move}\n";
The move the side to move should make, by looking ahead; undef once the game is over. It is "search" in Game::Fanorona::Rules with its steps described as steps describes them, and with move, the steps as one string: a whole move from between moves, the rest of the move from the middle of one, and the empty string when the thing to do is stop. Game::Fanorona::Bot is the player built on this.
evaluate
The position scored for White with no looking ahead. See "evaluate" in Game::Fanorona::Rules.
moves
my @strings = $game->moves;
Every whole move made, in order, as strings.
in_progress
The move being made, as far as it has got, as a string; undef between moves.
as_text
my $text = $game->as_text(white => 'Rakoto', black => 'Rasoa');
The game as text: the variant, anything about it that is not the default, the names if given, the result if there is one, a standing offer of a draw, the steps of a move half made, and the moves. See "A game" in Game::Fanorona::Notation.
position
The position as it stands, as Game::Fanorona::Engine writes it.
position_counted
The same with the two counts of a game in progress after it: the quiet moves running, and the number of the move about to be made.
at
$game->at('e3')
white, black, or undef for an empty point or a name that is not a point of this board.
stones
$game->stones('black')
How many stones a side has.
ply
Whole moves completed.
quiet
How many of them, running up to now, took nothing.
quiet_limit
How many that may be before the game is drawn.
repeats
How many times the present position has occurred.
in_chain
True while a move is in progress.
chain_at
The point of the stone in the middle of its move, or undef.
signature
Sixteen hex characters that are the same for the same stones on the same points with the same side to move, and different otherwise.
variant
The Game::Fanorona::Variant this game is played under.
WHOSE RULES THESE ARE
Fanorona has no governing body and no single rule book. The rules played here were taken from two texts, read in October 2026:
the article "Fanorona" in the English Wikipedia, revision 1375518715 of 18 September 2026;
Schadd, Winands, Uiterwijk, van den Herik and Bergsma, "Best Play in Fanorona Leads to Draw", New Mathematics and Natural Computation 4(3), 2008, the paper that solved the game.
Both give the board, the set-up of the nine by five, the step, the two ways of taking, that taking is compulsory, that a step which could take both ways takes one of them at the mover's choice, and the chain with its two limits. Where they are silent, or where a program needs a number they do not give, this distribution chose, and says so:
- A side that cannot move has lost
-
No traditional source says what happens. The paper had to decide and decided this, and so does this distribution. A game ended this way reports
no_moves. - The third time a position occurs, the game is drawn
-
The paper gives repetition as how a draw comes about and gives no count. Three is the count here, the position compared between moves and with the same side to move. It can be changed, or turned off, in a Game::Fanorona::Variant.
- Sixty moves running that take nothing, and the game is drawn
-
This is not a traditional rule. Without it a game between two players who cannot finish does not end. Sixty counts the moves of both sides, was chosen by playing games and not derived from anything, and may change in a later version. It too belongs to the variant.
- The set-ups of the two smaller boards are derived
-
The texts read give the set-up of the nine by five only. The five by five and the three by three are set up here by carrying its pattern down: Game::Fanorona::Variant draws them. The three by three set up this way is a win for White with best play, which is what the paper reports for that board.
Two things some descriptions of the game have are not here. No rule limits the first move: neither text has one. And the vela, the handicap game that the loser of a game is given before the next, is not played.
AUTHOR
LNATION <email@lnation.org>
BUGS
Please report any bugs or feature requests to bug-game-fanorona at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Game-Fanorona.
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)