NAME

Game::Schnapsen - Schnapsen and Sixty-Six, two games and one engine

VERSION

Version 0.01

SYNOPSIS

use Game::Schnapsen;

my $game = Game::Schnapsen->build(
    variant => 'schnapsen',
    seed    => $thirty_two_bytes,
    dealer  => 'p1',
);

$game->turn;                 # 'p2', the non-dealer leads
$game->legal('p2');          # what they may do

my @out = $game->apply('p2', { kind => 'lead', card => $id });

$game->scores;               # { p1 => 7, p2 => 5 }
$game->to_win('p2');         # 5
$game->over;                 # 1 once somebody reaches the target
$game->winner;

DESCRIPTION

Schnapsen is the Austrian two-player point-trick game, and Sixty-Six is its German parent. They share a pack, a card point table and the whole shape of a deal, and differ in thirteen places that Game::Schnapsen::Variant enumerates. Both are implemented here, and variant decides which is being played.

It is the engine behind the two games at https://peer2peergames.com.

The engine does no input and no output: it never prints, never reads a handle and never calls rand. A match is a pure function of its seed and its moves, so a finished match can be replayed and checked by anybody once the seed is published. Game::Schnapsen::Terminal is the only part of this distribution that touches a handle.

The score runs the way its game runs

Sixty-Six counts up from nothing: "The first player whose cumulative score reaches 7 or more game points wins the game."

Schnapsen counts down: "Both players start with 7 game points, and subtract the game points they win. The overall winner is the first player whose score reaches or passes zero."

scores is one number per player in that game's own direction, and it is not normalised. A Schnapsen player's score really is 7, then 5, then 2, then 0, which is what a scoreboard shows and what a rules page says. Storing points won and presenting a direction would give the engine and its consumer two numbers that can drift apart, so "match_over" in Game::Schnapsen::Variant is a predicate on the game's own number instead.

That makes zero a winning score in Schnapsen rather than a starting one. A consumer guarding a score with a plain truth test finds it false at exactly the moment it matters. to_win exists so that nothing downstream has to do this arithmetic, and so that no template has to do arithmetic at all.

Who deals next

Schnapsen alternates. Sixty-Six gives the deal to the winner of the deal.

A drawn Sixty-Six deal has no winner, so there is nobody to give it to and the dealer deals again. Neither page says so, because neither page raises the question; it is an inference, and it is recorded here as one rather than presented as sourced.

A drawn deal has to travel

Only Sixty-Six can draw a deal, and when it does the deal pays nobody, ends nothing and is played again. Four separate things have to be right: it must not move the score, must not end the match, must not rotate the dealer, and must still advance the deal number. Nothing else on this engine's roster has a drawn deal inside a match, so it is the result most likely to be dropped on the way through.

METHODS

build

Game::Schnapsen->build(variant => ..., seed => ..., dealer => ...);

A new match, or a Game::Schnapsen::Error for a variant this engine does not play or a seed that is not 32 bytes. dealer is who deals the first deal and defaults to p1; the other seat leads to the first trick.

variant, seed, dealer, number, deal

What the match was built with, who is dealing now, which deal this is, and the Game::Schnapsen::Deal in progress.

turn, legal, apply

Delegated to the current deal, and empty or refused once the match is over.

apply returns the deal's outcomes, and when a deal ends it appends the deal_end the deal produced and then either a deal for the next one or a game_end. So a caller never has to ask whether a deal has finished.

scores, to_win

The running score for each seat, in its game's direction, and how much each still needs. to_win never goes below zero, because a Schnapsen score can pass the target rather than land on it.

Note for a consumer whose interface wants richer scores: this is a plain number per seat. Anything with a shape to it is the consumer's to build.

deals

Every finished deal's result, in order, each with the number it was.

over, winner, result

Whether the match has finished, who won, and the whole verdict as { winner, loser, scores, deals }.

variants

The variant names this engine plays, sorted.

other

Game::Schnapsen::other('p1');   # 'p2'

The other seat.

SEE ALSO

Game::Schnapsen::Variant for the thirteen differences between the two games, Game::Schnapsen::Deal, Game::Schnapsen::Scoring.

Game::Gin, Game::Dominoes and Game::Cribbage are built the same way.

AUTHOR

LNATION, <email@lnation.org>

BUGS

Please report any bugs or feature requests to bug-game-schnapsen at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Game-Schnapsen.

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

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