NAME

Game::Durak::Search - what to play, from what one seat can see

VERSION

Version 0.01

SYNOPSIS

use Game::Durak::Search qw(best);

my $move = best($game->view($seat), 3, $word);
$game->apply($seat, $move);

DESCRIPTION

best takes a view, which is what one seat can see: its own hand, the bout, the trump, the turn-up while it is in the talon, how many cards are in the talon and the heap, how many cards the other seat holds, and the list of legal moves. It returns one of the entries of that list.

Blind by construction

Nothing in this file takes a game, another hand, the talon's order or the seed, and t/16-search-blind.t is a grep over the source rather than a test of behaviour, because a search that peeks is behaviourally indistinguishable from a search that is good. The same grep covers Game::Durak::Bot.

The one input that is not the position is $word, a 32-bit integer the caller draws from the game's seed, so that a bot's choice is a function of the position and the seed and a replay reproduces it.

It has no memory, and that is a decision

The view carries the heap as a count, never a list, because the rules say a player may not look through it. So this search cannot count the trumps that have gone, and neither can a person looking at the same screen. It is a ceiling on how strong the top rung can be, and if the rungs fail to separate the thing to revisit is that decision rather than the judgements below.

The three rungs

Rung 1, random

A uniform choice from legal. It attacks with its best card, trumps a six, and takes bouts it could have beaten. The floor of the bag.

Rung 2, cheap

One rule per decision. Beat with the cheapest card that beats. Attack and throw in with the cheapest card there is. Prefer a plain card to a trump, and prefer not to break a pair, because a pair is two throws later. Take only when nothing beats. Always exchange the trump six.

Rung 3, careful

Rung 2 plus two judgements, and both of them were measured rather than argued:

Take rather than spend a high trump early. While the talon is deeper than six and the bout is one or two cards, a trump above the nine buys one card and costs the endgame. Worth about nine points of the fool rate: a rung 3 without it loses 67 per cent of its deals against rung 2 instead of 58.

Attack out of a pair, defend out of a singleton. Rung 2 pays one more for a card whose rank it holds twice, because breaking a pair costs a later throw. Rung 3 turns that around when it is attacking, where a pair is not something to keep but something to spend: the second card goes in on the same bout, which is two cards shed instead of one.

The cost function carries all of this: a trump costs twenty more than any plain card, so cost never confuses the two, and the pair adjustment is one or two either way, so it only ever settles a near-tie.

Two judgements the measurement threw out

An earlier rung 3 also held back its good cards unless the other seat was down to one or two, and refused to open a bout with a trump while the talon had cards in it. Both are the sort of rule that sounds right.

Holding back cost ten points of the fool rate, and it deserves its explanation: in durak a card thrown into a bout that is beaten off is a card gone, and shedding is the whole game. A bot that keeps its good cards keeps its cards.

Refusing to open with a trump changed nothing at all, to the deal: the cost function already sorts every trump behind every plain card, so the rule never once chose differently from the rule it was layered on. It was removed as dead rather than kept as decoration.

FUNCTIONS

Nothing is exported by default.

best

best($view, $level, $word);

A move from $view->{legal}, or undef if there is nothing legal. A level below one is one and a level above "LEVELS" is "LEVELS", because a consumer's idea of how many levels there are is not this module's business.

LEVELS

Three.

THE TWO NUMBERS

our $DEEP_TALON  = 6;
our $CHEAP_POWER = 3;

How deep the talon has to be, and how high a trump has to be, before rung 3 would rather take the bout than spend the card. They are package variables and not constants because they are measurements and a later one may move them: 6 and 3 were chosen over 0 and 1, 0 and 3, 12 and 3 and 0 and 5 by running bin/ladder over each.

The difference between them is small and it is real. It is also invisible at four hundred deals: every one of those settings measured between 46.7 and 48.7 per cent with a two sigma interval of 5.1, and only at two thousand deals did 6 and 3 separate (47.4 per cent, two sigma 2.3) while 0 and 1 came out at exactly even.

SEE ALSO

Game::Durak::Bot, Game::Durak.

AUTHOR

LNATION, <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under the Artistic License 2.0.