NAME

Game::Merrills::Bot - a player that chooses its own moves, at five strengths

VERSION

Version 0.03

SYNOPSIS

use Game::Merrills;
use Game::Merrills::Bot;

my $game = Game::Merrills->new;
my $bot = Game::Merrills::Bot->new(level => 3, seed => 7);

while ($game->status eq 'active') {
	$game->move($bot->choose($game));
}

$bot->last_search;        # { depth, nodes, score, forced, move, pv }

DESCRIPTION

A bot looks at a game and picks a move for the side whose turn it is. It does not play the move; hand what it chose to the game.

It only ever picks a move the game offered

What "choose" returns is one of the objects in the game's own list of legal moves. The bot never makes a move up, so however poor its judgement, it cannot play an illegal one.

It looks ahead

The bot tries each move, then each reply, and so on to a depth set by its level, and picks the move that leaves it best off if both sides play as well as it can see. When the look-ahead ends on a position where a mill is about to be closed, it looks a little further before judging, so that it does not stop one move short of losing a man.

How it judges a position

At the end of each line it adds up what it likes and takes away what its opponent would like:

  • Men, on the board and in hand. Far the largest part.

  • Two men in a row with the third point empty, and more when a man is placed to step in and close it.

  • Room to move, once every man is placed.

  • While men are still being placed, the points where most lines meet.

That is all of it, and the list is short on purpose. Closed mills, men with no move and the running mill were each given a worth of their own and each was measured to change nothing: a bot with the term played no better than the same bot without it, at the weakest level over 800 games and at level 3 over 96. What those things are worth the bot finds by looking ahead to the men they win.

A side reduced to two men, or left with no move, has lost, and a loss sooner counts for more than a loss later. A position that would be a draw by repetition or for want of a mill counts as level.

Its effort is measured in positions, never in time

Each level has a depth and a number of positions it may look at. When the number runs out the bot answers with the best move from the deepest look it finished. No clock is consulted, so the same bot on the same game chooses the same move on any machine, however fast or slow.

It never guesses

Nothing here is random. Levels 1 and 2 are made to vary their play, and to play less well, by adding a small amount to each move's worth that is worked out from the seed, the move and how far the game has gone. A different seed gives a different game. From level 3 up the seed changes nothing.

PROPERTIES

level

Read only number, 1 to 5, weakest first. Defaults to 3. Anything else dies.

Each level was measured to beat the one below it, a draw counting half: 2 scores 79% against 1 and 3 scores 79% against 2 over 400 games each, 4 scores 67% against 3 over 200, and 5 scores 64% against 4 over 96. Good play at this game draws, so the gaps narrow at the top. A level 5 move takes several seconds.

my $bot = Game::Merrills::Bot->new(level => 1);

seed

Read only number that varies the play of levels 1 and 2. Defaults to 0.

my $bot = Game::Merrills::Bot->new(level => 1, seed => 42);

transposition

Read and write flag. The bot remembers positions it has already judged, so as not to judge them again when a different order of moves reaches them. Turning this off makes it slower. Given positions to spare, it does not change what the chosen move is judged to be worth, though between two moves of equal worth it may settle on the other. Defaults to true.

$bot->transposition(0);

A hashref describing the most recent choice, or undef before the first:

depth

How many moves ahead the finished look went.

nodes

How many positions were looked at.

score

What the chosen move was judged to be worth, from the chooser's side: 100 is about a man. Undef when the move was forced.

forced

True when there was only one legal move and nothing to think about.

move

The chosen move, as written.

pv

An arrayref of the moves the bot expects, its own and the replies, as written.

$bot->last_search->{score};

METHODS

choose

The move the bot would play in a game, one of the game's own legal moves, or undef when the game is over. The game is not changed.

my $move = $bot->choose($game);

levels

The levels there are, as a list, weakest first.

my @levels = Game::Merrills::Bot->levels;

setting_for

A hashref of what a level is allowed: depth, nodes and jitter, true for the levels the seed varies. Dies when the level is not one.

my $setting = Game::Merrills::Bot->setting_for(3);

PACKAGE VARIABLES

%LEVEL

What each level is allowed.

$MAN, $OPEN_TWO, $REACH, $MOBILITY, $JUNCTION

What each thing the bot likes is worth. They are variables so that they can be tried at other values.

CONSTANTS

WIN

The worth of a won game.

INFINITY

A number larger than any worth.

MAX_EXTENSION

How many moves past its depth the bot will follow mills being closed.

MAX_PV

The longest line of expected moves kept.

EXACT
LOWER
UPPER

How sure a remembered judgement is.

RM_FROM
RM_TO
RM_REMOVE
RM_CLOSES

The indexes of a raw move, as in Game::Merrills::Move.

HAND_WHITE
HAND_BLACK

Where a position holds each hand, as in Game::Merrills::Rules.

AUTHOR

LNATION, <email at lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)