NAME
Game::Merrills - Nine Men's Morris as an engine
VERSION
Version 0.03
SYNOPSIS
use Game::Merrills;
my $game = Game::Merrills->new;
$game->turn; # 'white'
$game->phase; # 'placing'
$game->legal_moves; # the 24 opening moves
$game->move('d2'); # a Game::Merrills::Move
my $bad = $game->move('d2');
$bad->message if ref $bad eq 'Game::Merrills::Error';
$game->status; # 'active' until somebody wins
$game->result->stringify; # 'White wins: Black has no move'
DESCRIPTION
Nine Men's Morris, also called Merrills, Merels and Mill: two players, nine men each, and a board of three squares joined at the middle of their sides.
A game object holds one game. It knows whose turn it is, what may be played, and when the game has ended and how. It reads and writes nothing, keeps no clock and never guesses, so the same moves always make the same game.
It is the engine behind the Nine Men's Morris at https://peer2peergames.com.
The rules as played here
White moves first.
Each side in turn places a man on any empty point until all eighteen are down. After that a turn moves one man along a line to the next point, if it is empty.
Three men of one side in a row along a line make a mill. The move that completes one takes an enemy man off the board, and that man is named as part of the move. A man in a mill cannot be taken while any enemy man stands outside one. A man that completes two mills at once takes one man, not two.
A side with exactly three men left and none in hand flies: its men move to any empty point. This is the usual rule and can be turned off with "flying".
A side with fewer than three men has lost, and so has a side whose turn it is and which has no move.
Draws
Printed rules for this game give no draw, and two careful players can shuffle for ever, so this module adds three:
The same men on the same points with the same side to move, for the third time.
"NO_MILL_PLIES" moves in a row, counting both sides, with no mill closed.
One side offers a draw and the other accepts.
Both counts begin once every man is placed. A position cannot come round again while men are still going down, or after a man has been taken, so each placement and each capture starts both counts afresh.
Refusals
A move the rules do not allow is not an exception. "move" returns a Game::Merrills::Error saying why, and the game is unchanged. What dies is a mistake in the calling code: a side that is not one, a position that could not be one, a written game that does not play.
The game to play
The merrills script plays the game in a terminal, against a bot at one of five strengths, between two people, or bot against bot. merrills --help lists what it takes. It is Game::Merrills::Terminal and nothing more.
The modules
- Game::Merrills::Points
-
The 24 points, the lines between them and the 16 mills.
- Game::Merrills::Board
-
The men on the points and the men still in hand.
- Game::Merrills::Move
-
One whole move: where from, where to, and the man it took.
- Game::Merrills::Notation
-
Moves, positions and whole games as text.
- Game::Merrills::Rules
-
Which moves are legal, and what a move does to a position.
- Game::Merrills::Error
-
A move the game refused, and why.
- Game::Merrills::Result
-
How a game ended.
- Game::Merrills::Bot
-
A player that chooses its own moves, at five strengths.
- Game::Merrills::Terminal
-
The game at a prompt, on a drawn board. The only module that reads or writes.
PROPERTIES
Give position and flying to new. The rest are the game's own record and are there to be read.
position
The position the game began from, as Game::Merrills::Notation writes one. Defaults to the empty board with white to move. "to_position" gives the position as it stands now.
my $game = Game::Merrills->new(position => 'WWW.BB..B..W....B..W.B.. w 4 4 0 10');
flying
Whether a side down to three men flies. Defaults to true.
my $game = Game::Merrills->new(flying => 0);
turn
The side to move, white or black.
$game->turn;
result
The Game::Merrills::Result once the game has ended, and undef before.
$game->result;
history
An arrayref of the Game::Merrills::Move objects played, in order.
$game->history;
no_mill
How many moves in a row have been played without a mill being closed.
$game->no_mill;
repetition
A hashref counting how often each position has stood since the last placement or capture.
$game->repetition;
draw_offered_by
The side with a draw offer standing, or undef.
$game->draw_offered_by;
METHODS
Where a method takes a side and is given none, it means the side to move.
status
active while the game is going and finished once it has a result.
$game->status;
ply
How many moves have been played, counting each side's as one, and counting from the ply of the position the game began from.
$game->ply;
phase
What the side to move does on its turn: placing, moving or flying.
$game->phase;
phase_of
The same for a named side. One side can be flying while the other is not.
$game->phase_of('black');
in_hand
The men a side has yet to place.
$game->in_hand('white');
on_board
The men a side has on the board.
$game->on_board('white');
men
The men a side has left, on the board and in hand together.
$game->men('white');
board
The men as a Game::Merrills::Board, a copy that can be changed without touching the game.
$game->board->in_mill($point);
legal_moves
An arrayref of every Game::Merrills::Move the side to move may play, in a fixed order. A move that closes a mill is there once for each man it could take. Empty once the game is over.
my $moves = $game->legal_moves;
legal_moves_for
The legal moves of the man on a point, as an arrayref. While men are being placed, the legal moves that put a man on that point.
my $moves = $game->legal_moves_for($point);
move
Plays a move for the side to move. Takes a Game::Merrills::Move, a string such as d2, d2-d3 or d2-d3xa1, or a hashref of from, to and remove whose values are point numbers or coordinates. Returns the move played, or a Game::Merrills::Error when it is refused.
A move that closes a mill must say which man it takes.
$game->move('d2-d3xa1');
$game->move({ from => 'd2', to => 'd3', remove => 'a1' });
undo
Takes back the last move and returns it, lifting any result the game had reached. Returns a Game::Merrills::Error when no move has been played.
$game->undo;
resign
A side gives up and the other wins. Returns the result, or a Game::Merrills::Error when the game is already over.
$game->resign('black');
timeout
A side has run out of time and the other wins. Returns the result, or a Game::Merrills::Error when the game is already over.
$game->timeout('black');
offer_draw
A side offers a draw. The offer stands until the other side accepts it, declines it or plays a move. Returns the side.
$game->offer_draw('white');
accept_draw
A side accepts the draw the other offered, and the game ends. Returns the result, or a Game::Merrills::Error when there is no such offer.
$game->accept_draw('black');
decline_draw
A side turns down the draw the other offered. Returns the side, or a Game::Merrills::Error when there is no such offer.
$game->decline_draw('black');
to_position
The position as it stands, as one line of text.
my $text = $game->to_position;
from_position
A new game beginning from a position. Further arguments go to new. A game begun this way has no earlier moves to take back and no earlier positions to repeat.
my $game = Game::Merrills->from_position($text, flying => 0);
to_text
The whole game as text: the moves in numbered pairs, after a line naming the position it began from when that was not the empty board and a line saying so when the game is played without flying, and before a line naming the side whose draw offer stands and a line naming the winner and the reason when the game was resigned, timed out or agreed drawn. "from_text" builds the same game back from it, counts, offer and ending included.
my $text = $game->to_text;
from_text
A new game played through from its text, with its draw offer and its ending put back after the last move. Further arguments go to new, though a flying line in the text overrules a flying argument. Dies, naming the move, when one of them is refused, and dies saying why when a line beside the moves cannot be read or names an ending the moves have already reached.
my $game = Game::Merrills->from_text($text);
clone
A second game in the same state that shares nothing with the first. Moves played on one, or taken back, leave the other alone.
my $copy = $game->clone;
CONSTANTS
NO_MILL_PLIES
120. The number of moves in a row, counting both sides, that may pass with no mill closed before the game is drawn.
The number was measured, twice. Over 2,000 games between two simple players that take a mill when they can and block one when they must, with no limit at all, none of the games that ended by themselves went more than 89 moves without a mill. This module's own bot is more patient: of 431 of its games that ended by themselves, six went more than 100 moves without a mill before somebody won, and one went more than 120. So 100 was too few and 120 is what ships. xt/draw-measure.t holds the number above what the simple players need.
PACKAGE VARIABLES
$NO_MILL_LIMIT-
The limit in force, "NO_MILL_PLIES" unless changed. Set it to 0 and no game is drawn for want of a mill, which is of use when measuring how long games run and of none when playing them.
CAVEATS
Carp is loaded before Object::Proto::Sugar on purpose. In a plain script, under Devel::Hook before 0.011, the other order fails to compile.
AUTHOR
LNATION, <email at lnation.org>
BUGS
Please report any bugs or feature requests to bug-game-merrills at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Game-Merrills. 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::Merrills
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.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)