NAME

Game::RoyalUr::Bot - an opponent for the Royal Game of Ur, at several strengths

VERSION

Version 0.01

SYNOPSIS

use Game::RoyalUr;
use Game::RoyalUr::Bot;

my $game = Game::RoyalUr->new(seed => $seed);
my $bot  = Game::RoyalUr::Bot->new(level => 2);

until ($game->is_over) {
    $game->play($bot->choose($game));
}

DESCRIPTION

Something to play against. A bot is handed a game with a roll waiting and answers with one of the game's own legal moves. It does not make the move, keeps nothing between one move and the next, and the same bot asked the same question gives the same answer.

The levels

A level is a rung of a ladder, 1 the weakest. Each rung was played against the one below it over thousands of games with the same dice from both seats, and is on the ladder because it won: a rung that could not be told from the one below it was taken out, not renamed.

The two routes have different ladders, because what wins on one does not always win on the other.

Level 1 does not look ahead. It captures if it can, lands on a rosette if it cannot, and otherwise moves its piece that is furthest along.

Every level above it looks ahead through the dice: it weighs every roll that could come by how likely it is, supposes each side then makes the move that is best for it, and so on for a number of rolls. Higher levels look further. They also value more than how far the pieces have come: they count what a piece left where it can be captured stands to lose, and, where the rules make a rosette safe, the worth of holding one.

Dice are dice. A higher level wins more often, and still loses a great many games to a lower one.

What a move costs

A search is bounded by the number of positions it may look at and never by the time it takes, which is why its answer does not depend on the machine. budget is that number. Left alone it is large enough that no level is cut short; set lower, a search that runs out answers from as far as it got.

METHODS

new

my $bot = Game::RoyalUr::Bot->new;
my $bot = Game::RoyalUr::Bot->new(level => 1, budget => 50_000);
level

A whole number from 1 up. Left out, the bot plays at the top of whatever ladder the game's rules have; set higher than that ladder goes, it plays at the top.

budget

The most positions one search may look at, from 1 to 2,000,000,000. One million when it is left out.

Croaks on a level or a budget that is not such a number.

level

The level the bot was made with, or undef for the top.

budget

The most positions one search may look at.

choose

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

One of the moves $game->legal offers, or undef when the game has none to offer because it is over.

think

my $thought = $bot->think($game);

As choose, with what the bot knows about its answer, as a hash reference:

{ move => $move, level => 3, index => 1, depth => 3, value => 812,
  nodes => '3391', stopped => 0 }

level is the level played, index the move's place in $game->legal, and the rest are as "search" in Game::RoyalUr::Engine returns them. At level 1 there is no search: depth is 0 and value is undef.

levels

my @levels = Game::RoyalUr::Bot->levels('masters');

The levels there are for a rule set, 1 to however many. A rule set with no name has the ladder of its route.

level_for

my $level = $bot->level_for($rules);

The level this bot plays at under a rule set: its own, or the top of that ladder if its own is higher or was left out.

rung

my $rung = Game::RoyalUr::Bot->rung(2, 'finkel');

What a level is, as a hash reference: { greedy => 1 } for a level that does not look ahead, or { depth => ..., weights => { ... } }. A copy. Croaks on a level the rule set's ladder does not have.

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)