NAME
Game::Reversi - the board game of 1883, with its own opening
VERSION
Version 0.01
SYNOPSIS
my $game = Game::Reversi->new(variant => 'historic', seed => $bytes);
while ($game->status eq 'active') {
my $moves = $game->legal($game->turn);
my $result = $game->play($game->turn, $moves->[0]->square);
die $result->message if ref $result && $result->error;
}
$game->winner; # 'b', 'w', or undef for a tie
$game->to_text; # the transcript
DESCRIPTION
Reversi. Two players, an 8x8 board, and discs that turn when they are outflanked.
It is the engine behind the reversi at https://peer2peergames.com.
This is Reversi, not Othello, and the difference is the opening
They are different games. Wikipedia:
Reversi is an abstract strategy board game for two players, played on an 8x8 uncheckered board. It was invented in 1883. Othello, a variant with a fixed initial setup of the board, was patented in 1971.
Othello starts with four discs already on the two centre diagonals. Reversi starts empty and the players place those four discs themselves, capturing nothing. That opening is the default here, and it is why the distribution can carry this name: "Othello" is a registered trademark, owned outside Japan by MegaHouse, while Reversi is the free ancestor it was built from.
The historic opening reaches six distinct positions, of which the Othello position is one. See Game::Reversi::Opening. The Othello start is available as variant => 'othello'.
Refusals are returned, never thrown
Every rejection a player can cause comes back as a Game::Reversi::Error, so a caller tests a return value and never wraps a move in eval. die is kept for programmer error.
The move log is the canonical serialisation, not the position
Two identical boards can have different sides to move, depending on whether a turn was just forfeited. So a position alone cannot resume a game. That is a fact about this game's rules rather than a preference about event sourcing.
METHODS
new
variant defaults to historic. seed is stored and never consulted; see "seed".
from_board
A game from a position rather than from the beginning. Takes the board and optionally turn, variant and seed. It is new with the board handed in: turn is the side to move if it can, and the rules decide if it cannot. A colour that cannot move in that position does not get the turn; the rules forfeit for it.
The position is recorded in the start event, so such a game still replays, and "replay" refuses a log that began from a different one.
variant, board, status, turn, winner, seats, phase, log
The state. phase is opening while the first four discs are being placed and play afterwards. winner is undef for a tie.
log is the events as the game holds them, and "events" is a copy of that list: append to the one this returns and you are appending to the game's own record of itself.
seed
The seed, and only once the game is finished. It is held in a private property rather than a plain one, so this gate is the only way to it.
Reversi has no randomness in it at all, so this is never consulted. It is stored because the site that consumes this engine hands every game 32 bytes and publishes them at the end so that a finished game can be checked; an engine that dropped the seed would quietly break that page for this one game. Do not remove it on the grounds that nothing reads it.
result
A Game::Reversi::Result once the game is over, and undef before.
places
The finishing order, 1 for the winner and 2 for the loser, both 1 on a tie. undef while the game is running: a Reversi position in progress has no standings worth the name, because the side ahead on discs in the midgame is very often the side losing.
score
The official score, and only once the game has reached its own end. See "timeout" for why a game that was stopped does not get one.
timeout
Ends the game, with the seat named as the one that ran out and the other as the winner.
The engine takes no view on what a timeout is worth. The sources give three different answers: WOF's championship rules guarantee the non-defaulting player at least 33-31, the same document scores an abandoned game 64-0, and Wikipedia describes a common procedure guaranteeing only a one disc margin while conceding that "There are varying methods to determine the official score when a player defaults". A timeout is the host talking, and whatever is running the game already has a policy. So the result names the winner, counts says what was on the board, and "score" is undef.
This matters far more in correspondence play than over a board, where a default is rare.
abandon
Ends the game with no winner at all.
resign
Ends the game with the other seat as the winner. Unlike a timeout this is a move a player made, so it appears in the log as theirs rather than as the engine's.
counts
Discs on the board, by colour. Not the final score: a game that ends with squares still empty awards them to the winner. The two are separate names on purpose, so that a midgame reading cannot reach for the end of game rule.
scores
The same counts in the shape the site wants.
legal
The moves open to a seat, as Game::Reversi::Move objects. Placements during the opening, plays afterwards, and never a pass. Empty for the seat not on turn and empty once the game is over.
waiting_on
The seat on turn, or nothing.
play
Plays a square. Returns the Game::Reversi::Move made, or a Game::Reversi::Error.
pass
Always an error, has_move. WOF rule 2 forbids forfeiting a turn you can play, and a seat on turn always can, because the engine forfeits automatically for a seat that cannot. There is no way to reach this method legitimately, and it exists so that a caller which offers a pass button gets a sentence to show rather than silence.
clone
A deep enough copy to search from: the board and the log are copied and everything else is shared. It copies the object rather than building a new one, because a constructor would deal the opening again and emit a start event, and the state worth cloning is exactly the state a constructor does not take.
events
The log: sys start, place, play, sys pass, sys game_end.
replay
Rebuilds this game from a log. Only the player events are applied; every sys event is regenerated from the position, and the resulting log must then match what was handed in. So a log carrying a pass at a position where that seat could have moved is refused, which matters because a forged pass is the cheapest possible cheat in this game: it hands the opponent's turn back to you.
to_text, from_text
The squares played, in order, as a transcript. Placements and plays alike, since both are squares and which is which follows from how many came before.
SEE ALSO
Game::Reversi::Board, Game::Reversi::Opening, Game::Reversi::Rules, Game::Reversi::Move, Game::Reversi::Notation, Game::Reversi::Error.
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.