NAME
Game::Schnapsen::Deal - one deal, its talon, and its two phases
VERSION
Version 0.01
SYNOPSIS
my $deal = Game::Schnapsen::Deal->build(
variant => 'schnapsen',
seed => $thirty_two_bytes,
number => 1,
dealer => 'p1',
);
$deal->trump; # the suit of the turn-up
$deal->phase; # 1 while the talon is open, 2 after
$deal->legal("p2"); # [ { kind => "lead", card => 7 }, ... ]
$deal->apply('p2', { kind => 'lead', card => 7 });
DESCRIPTION
A deal of Schnapsen or of Sixty-Six. The non-dealer leads to the first trick.
The state of this module
A whole deal is implemented: leading, following, winning a trick, drawing, marriages, the trump exchange, closing the talon, claiming, and the four ways a deal can end. What is not here is the match around it, which is Game::Schnapsen's: deal rotation, the running game-point score, and who wins the whole thing.
A declaration does not consume the turn
A marriage, a trump exchange and a close all belong to the player whose turn it is to lead, they happen before that lead, and afterwards that player is still on lead. So turn does not change, and a lead follows.
Riding a declaration on the lead itself, as { kind => 'lead', card => $id, marriage => 1 }, cannot express the game: both rulesets let a player declare a marriage and go out immediately on the strength of the twenty or forty, without leading at all.
The cost is a round trip, and only in the turns where a declaration happens.
Why the draw is not always automatic
In Sixty-Six the talon may be closed either before or after drawing, and in Schnapsen only after. So Sixty-Six has a moment that Schnapsen does not: the trick is over, the winner is on lead, and the draw has not happened yet. At that moment legal offers exactly close and draw, and drawing is how a player declines to close.
Schnapsen has no such moment, so it draws by itself and draw is never a move there. pending_draw says which state a deal is in.
This costs Sixty-Six one extra turn per trick, which is worth knowing before measuring how long a match takes.
It would be cheaper to draw immediately in both games and let the player close afterwards, and that is a different game: the choice to close before drawing is made without having seen the card you would have drawn, and offering it after the draw hands the closer information the rules do not give them.
A marriage does not count until its owner takes a trick
Declaring is recorded in melds as pending. The twenty or forty is added to taken at the moment that player first takes a trick, and a player who declares and never takes one scores nothing for it. Declaring when a trick has already been taken counts at once.
This is the rule most often got wrong, and getting it wrong gives a deal that looks right all the way through and is simply worth more than it should be.
The close-time exchange is applied rather than offered
In Sixty-Six, closing the talon lets the opponent exchange the trump nine at that moment, even having won no trick. That is an action belonging to the player who is not on lead, and making it a move would need the turn to change hands in the middle of one.
It is applied automatically instead. The nine is worth no card points and the turn-up is worth between two and eleven, so taking the exchange is never worse and declining is never right, which makes it not a decision worth a turn. It still produces its own exchange outcome, so a log and a replay show it happening.
This is a deliberate deviation from the letter of the source, which offers it, and it is recorded here rather than left to be discovered.
The two phases
phase is 1 while anything remains to be drawn and 2 once nothing does, whether because the talon ran out or because somebody closed it. It is derived rather than stored, so it cannot disagree with the talon.
The rules of each phase are Game::Schnapsen::Trick's business and are the same in both games.
A draw is not a move
Both players draw after every trick, the winner first, and there is no choice in it at any point. So drawing is a consequence of a trick rather than a turn, it produces no event of its own, and a consumer's move log never records which card was drawn.
That is not a saving of bytes. The card drawn is the next off an order the seed already fixed, so it is derivable from the seed and the number of tricks played, and a payload that never held it cannot leak it however a log is filtered later. A consumer keeping hands secret gets that for nothing.
The winner draws first
With an odd number of face-down cards left this decides who receives the turn-up, which is the one card in the talon both players have seen. Both rulesets are explicit about the order, and getting it backwards gives a legal, replayable, entirely plausible game that is simply not the one in the book.
talon_left and draw_left are different numbers
talon_left counts the cards physically there. draw_left is how many may still be drawn, which is zero once the talon is closed even though the cards remain. The two are equal until somebody closes.
They are kept apart because a closed talon and an empty one play identically and score differently, so a single number would be right about the play and wrong about the result.
METHODS
build
Game::Schnapsen::Deal->build(variant => ..., seed => ..., number => ..., dealer => ...);
A new deal, or a Game::Schnapsen::Error for a variant this engine does not play, a seed that is not 32 bytes, or a deal number below one.
variant, seed, number, dealer, trump
What the deal was built with, and the trump suit the turn-up set.
hands, hand_of
The two Game::Schnapsen::Hand objects, and one of them by seat.
talon, turn_up
The face-down cards in draw order, and the face-up trump card, which is undef once it has been drawn.
talon_left, draw_left, closed, closed_by, close_state, drawn, pending_draw
How many cards are there, how many may be drawn, whether the talon has been closed and by whom, and where both players stood at the instant it was closed.
close_state is taken on every close in both games, as { by, p1 => { tricks, points }, p2 => { ... } }. Schnapsen scores the closer's opponent as they stood at that moment and Sixty-Six counts their whole deal, so only one of the two games reads it; taking it unconditionally costs nothing and keeps the Schnapsen path from being a special case that exists only sometimes.
drawn and pending_draw say whether the draw for the last trick has happened.
melds, must_lead
The marriages declared, as { seat, suit, value, counted }, and the two cards a player who has just declared one is now obliged to lead from.
can_exchange, can_close, can_marry, can_claim
What the seat on lead may declare: the id of the exchange card or undef, two booleans, and the marriages available. legal is built from these, and they are public because a consumer wants to explain why an option is absent.
can_claim is about timing and nothing else: "A claim may be made just after winning a trick or just after declaring a marriage, but not at any other time." It does not check whether the player actually holds 66, and it must not. A claim offered only when it would succeed makes the penalty for a false one unreachable, and a rule that cannot be broken is a rule that is not implemented.
phase
1 or 2.
tricks, last_trick, tricks_won
Every trick as { leader, lead, follow, winner }, the most recent, and how many a seat has taken.
taken, points_of
The card points each seat has taken. Both players count openly in this family, so these are public: it is the points still in a hand that are secret.
turn, leader, lead
Whose move it is, who led the current trick, and the card they led, which is undef between tricks.
cards_out, over, result
Whether the hands are empty, whether the deal has finished, and how it finished. result is Game::Schnapsen::Scoring's verdict, set the moment the deal ends, and undef before then.
A deal ends in one of four ways: somebody claims correctly, somebody claims wrongly, a closer runs out of cards without going out, or the cards run out with no close and no claim. The last of those is the one the two games disagree about most.
Note that a deal is never ended by arithmetic. Reaching 66 does nothing at all until a player says so, which is what makes a false claim possible and is required by both sources.
history
Every outcome apply has returned, in order. The deal's own record of itself, which is not the same thing as a consumer's move log and is not a serialisation: rebuilding from the seed and replaying the moves is.
other
Game::Schnapsen::Deal::other('p1'); # 'p2'
The other seat. A function rather than a method, because it is about the two seats and not about any one deal.
legal
$deal->legal($seat);
What that seat may do, as a list of { kind, card }. Empty for the seat that is not to move and for a finished deal.
apply
$deal->apply($seat, { kind => 'lead', card => $id });
Returns the outcomes as a list of hashrefs, or a single Game::Schnapsen::Error. Following a trick returns both the follow and the trick it completed.
SEE ALSO
Game::Schnapsen::Trick, Game::Schnapsen::Deck, Game::Schnapsen::Hand.
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.