NAME

Game::Schnapsen::Search - the bot's reasoning, with a signature that cannot cheat

VERSION

Version 0.01

SYNOPSIS

use Game::Schnapsen::Search qw(best_lead best_follow should_close);

my $card = best_follow(
    cards => $legal,       # the cards the rules already allow
    led   => $id,
    trump => 'H',
);

my $lead = best_lead(cards => $legal, trump => 'H', phase => 2, level => 2);

DESCRIPTION

Every function here takes a flat hash of values and nothing else. There is no game, no deal, no talon, no seed and no opponent's hand anywhere in any signature, and there is no way to reach one from what is passed in.

Why the signature is the defence

A bot that could read the talon would know every card it is about to draw and every card its opponent holds. It would then play legal, replayable, winning games that no ordinary test can tell from a very good player, which is exactly why an ordinary test cannot defend this. The cheapest defence available is a signature that cannot express the cheat, and that is what this is.

t/18-search-blind.t is a grep over this file rather than a behavioural test, for the same reason. It forbids the words, and it also asserts that the values that are passed in appear, so that it is not merely measuring an empty file.

What it is allowed to know

cards, which is the player's own hand or the subset of it the rules already permit; led, trump and turn_up, which are on the table; talon_left, which is a count and is public; my_points; and their_points and their_tricks, which are public because both players count openly in this family. A view that hid the opponent's card points would be a different game.

The caller passes the cards the rules already allow. Nothing here knows about following suit, about the two phases of a deal, or about when a talon may be closed. That keeps one copy of the rules, in Game::Schnapsen::Deal, and means a bot cannot play an illegal move by disagreeing with them.

The variant never arrives here

Not as a name and not as a predicate. The two games differ in what is legal and in how a deal is scored, and both of those are settled before this module is called. What is left is shared, so the search does not know which game it is playing.

Taking tricks is not optional in this family

best_follow takes every trick it can, at both levels, with the cheapest card that will do it, preferring one that is not a trump. That is not a simplification: all of it was measured.

An earlier level 2 declined to spend a trump on a cheap trick, which is ordinary advice in trick games where only some tricks score. Here it is badly wrong, and the measurement was not close: level 2 won 14.7% of matches against level 1 at Schnapsen and 8.7% at Sixty-Six, and removing that one rule turned those into 79.3% and 72.0%.

The reason is that this family scores the tricks themselves and not only their contents. A player with no trick at all concedes three game points, one with few concedes two, and nobody can go out at all without reaching 66 card points. Passing on a cheap trick starves you of exactly what the game counts, and hands the opponent the same thing.

Which card takes the trick is a separate question, and the answer is the opposite one. Spending a trump where a plain card would win is wasteful, and head to head a follow that prefers a non-trump winner beats one that simply takes the cheapest by card points, 75.5% at Schnapsen and 78.8% at Sixty-Six over 400 matches a side with the seats swapped.

So the rule is: never decline a trick, but do not pay a trump for one you could have had for nothing. The two are easy to conflate and only one of them is about conserving trumps.

The two levels, and which part of level 2 is doing the work

Level 1 takes every trick it can, throws its cheapest card otherwise, leads its cheapest card, and never closes the talon.

Level 2 plays the same cards to a trick, and differs in two places: it leads low while the talon is open and high once it is gone, and it closes the talon when its hand can finish the job.

Level 2 beats level 1 in about 75% of matches at Schnapsen and 71% at Sixty-Six, measured over 500 matches a side with the levels swapped between seats so a seat bias cannot be read as a level result.

Essentially all of that is the lead choice. With it removed, level 2 falls to 49% and 43%, which is parity or worse. Closing is neutral: 75.4% against 74.4% at Schnapsen and 71.0% against 73.0% at Sixty-Six, which is two small numbers of opposite sign.

Closing is kept even so. It is a real move in both games, it fires several hundred times across those matches, and a bot that never closed would never show a player one of the two signature moves of the family. What it is not is the reason level 2 wins, and nobody should later tune it believing it was.

Both levels claim only on a real 66. That is a property of the bot and not of the rules: claiming is offered on timing alone, so a bot that guessed would donate two or three game points and make a level ladder meaningless.

FUNCTIONS

Nothing is exported by default.

best_lead

best_lead(cards => \@legal, trump => $suit, phase => 1|2, level => $n);

Which card to lead.

best_follow

best_follow(cards => \@legal, led => $id, trump => $suit);

Which card to answer with: the cheapest card that wins the trick, or the cheapest card in hand if none does. The same at every level, for the reason above.

best_marriage

The suit of the most valuable marriage on offer, or undef. Declaring is never worse than not declaring, so there is no decision beyond which one.

should_close

Whether to close the talon. Level 1 never does. Measured as neutral rather than strong: see above before spending time on it.

should_claim

Whether to claim. True only on a real 66, at every level.

hand_strength

A rough worth for a hand: card points, plus five for a trump and three for an ace. Used by should_close and exposed because it is the part worth testing on its own.

LEVELS

How many levels this search offers.

SEE ALSO

Game::Schnapsen::Bot, Game::Schnapsen::Deal.

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.