NAME
Game::Schnapsen::Variant - the thirteen places the two games differ
VERSION
Version 0.01
SYNOPSIS
use Game::Schnapsen::Variant qw(deck_size exchange_rank match_over);
deck_size('schnapsen'); # 20
deck_size('sixtysix'); # 24
exchange_rank('schnapsen'); # 'J', the trump jack
exchange_rank('sixtysix'); # '9', the trump nine
match_over('schnapsen', 0); # 1, a countdown that has reached zero
match_over('sixtysix', 0); # 0, a count up that has not started
DESCRIPTION
Schnapsen and Sixty-Six are two published games that share a card point table and differ in thirteen places. This module is the whole of that difference.
Why it is one table in one file
The two rulesets are the easiest pair in the world to average into a third game that no publication describes, and a ruleset nobody publishes has no citable test vectors. The defence is structural rather than a promise to be careful: every divergence is a named field here, and nothing else in the distribution branches on a variant name. A rules function is given the predicate it needs and never the name of the game it is playing.
That has a consequence worth stating, because it reads like an oversight otherwise: every field in this table is a divergence. There is no entry for anything the two games agree on. Card point values, the values of a marriage, sixty-six itself and the one, two, three scale are shared, so they are implemented once elsewhere and are not parameters. If a rule turns out to be shared after all, it leaves this table rather than being recorded here as two equal values.
The thirteen
The pack. Twenty cards for Schnapsen, twenty-four for Sixty-Six, which adds the nines.
deck_sizeandranks.The hand. Five cards each and ten to draw, against six each and twelve to draw.
hand_size.The exchange card. The trump jack in Schnapsen, the trump nine in Sixty-Six: in each case the lowest trump in that pack.
exchange_rank.Whether the exchange needs a trick first. Sixty-Six requires that the player "has already won at least one trick"; the Schnapsen page states no such condition, giving only "This can only be done by the player whose turn it is to lead, just before he leads to the trick".
exchange_needs_trick.Marriages once the talon is closed or exhausted. Schnapsen allows them in any trick; Sixty-Six allows none from that moment.
marriage_after_close.The talon running out with no close and nobody out. Schnapsen pays the winner of the last trick one game point whatever the card points. Sixty-Six pays ten card points for the last trick, making 130 in the pack, and scores the higher total on the usual scale, which means a Sixty-Six deal can be drawn and a Schnapsen deal cannot.
last_trick_rule.Whose tricks are counted after a SUCCESSFUL close. Sixty-Six scores it on "the cards in the opponent's total tricks taken before and after closing"; Schnapsen on "the tricks the opponent had at the moment of closing".
close_counts_tricks_at. A failed close reads the moment of closing in both games, so this field must not reach that path.What a false claim costs. Schnapsen pays "2 game points, or 3 game points if the false claim is made before the opponent has taken a trick"; Sixty-Six pays a flat 2.
false_claim_schwarz.What beating a closer is worth. When the closer's opponent reaches 66 and claims first, Schnapsen says "The same scores of 2 or 3 game points apply", so it is scored exactly like any other failed close. Sixty-Six caps that one case at 2.
beaten_closer_flat.When the talon may be closed. Schnapsen only after drawing, at a full hand each; Sixty-Six either before or after.
close_before_draw.The exchange at the moment of a close. In Sixty-Six the opponent may take it even having won no trick; in Schnapsen they may not.
exchange_on_close.Who deals next. Schnapsen alternates; Sixty-Six gives the deal to the winner.
next_dealer.The match. Schnapsen starts both players at seven and subtracts, and is won by the first to reach zero or pass it. Sixty-Six starts at zero and adds, and is won by the first to seven or more.
match_startandmatch_target, withmatch_directionandmatch_overderived from them.
The score is stored the way the game is played
One number per player, counting the way that game counts. A Schnapsen player's score really is seven, then five, then two, then zero, and that is what a rules page will say and what a scoreboard will show. Storing points won and presenting a direction would give the engine and its consumer two numbers that can drift apart, so match_over is a predicate on the game's own number instead.
Note that this makes zero a winning score in Schnapsen rather than a starting one. A consumer guarding a score with a plain truth test will find it false at exactly the moment it matters.
FUNCTIONS
Nothing is exported by default.
variants
The variant names, sorted.
is_variant
Whether a string names a variant.
check_variant
Returns undef for a good variant and a Game::Schnapsen::Error with code bad_variant for anything else. This is the boundary: a caller validates once here, and everything below treats a bad variant as programmer error.
There is deliberately no default. A typo silently falling back to Schnapsen would ship the wrong game.
spec_for
The whole table for a variant, as a hashref. Dies for a name that is not a variant.
fields
The field names, sorted. A caller checking it has covered them all can use this.
deck_size, hand_size, ranks, exchange_rank, exchange_needs_trick, marriage_after_close, false_claim_schwarz, beaten_closer_flat, last_trick_rule, close_counts_tricks_at, close_before_draw, exchange_on_close, next_dealer, match_start, match_target
One field each, given a variant name. ranks returns a fresh arrayref, so a caller cannot edit the table.
match_direction
1 if the score counts up, -1 if it counts down. Derived from the start and the target rather than stored, so the three cannot disagree.
match_over
match_over($variant, $score);
Whether a score has finished the match, counting in that game's direction.
SEE ALSO
Game::Schnapsen, Game::Schnapsen::Deck.
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.