ginrummy

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2026 License: MIT Imports: 1 Imported by: 0

README

ginrummy

Pure-logic rules and scoring library for two-player Gin Rummy: meld detection (optimal set/run partition), deadwood counting, knock/gin/undercut scoring, lay-offs, and match play to 100.

Cards are plain ints 0..51: Rank(c) = c % 13 (0=Ace .. 12=King, aces low), Suit(c) = c / 13 (0=♠ 1=♥ 2=♦ 3=♣). Everything is deterministic — no randomness, no crypto, no networking — so callers can drive and verify games reproducibly.

deadwood, melds, unmatched := ginrummy.BestMelds(hand) // optimal partition
if ginrummy.CanKnock(hand) { // deadwood ≤ 10
	kPts, oPts := ginrummy.Score(knocker, opponent, ginrummy.IsGin(knocker))
	_ = kPts; _ = oPts
}

Compiles to WASM. Extracted from kibitz, where it scores Gin Rummy hands dealt via a dealerless mental-poker shuffle.

MIT licensed.

Documentation

Overview

Package ginrummy is a pure-logic rules and scoring library for two-player Gin Rummy, operating on plain card indices (0..51) with no crypto, networking, or service dependencies. Everything here is deterministic (no randomness) so callers can drive and verify games reproducibly.

Card model: a card is an int 0..51 where Rank(c) = c % 13 (0=Ace .. 12=King) and Suit(c) = c / 13 (0=♠, 1=♥, 2=♦, 3=♣).

Melds are the scoring groups: a set is 3 or 4 cards of the same rank, and a run is 3+ consecutive cards of the same suit with Ace LOW only — A-2-3 is a valid run but Q-K-A is not (there is no wraparound). BestMelds finds the decomposition that minimises leftover deadwood.

Lay-off: after a non-gin knock the defender may lay their own deadwood cards onto the knocker's melds, reducing the defender's counted deadwood before scoring (see LayOff). Score applies this in the non-gin branch, so the opponent deadwood (od) it compares is the post-lay-off remainder. Gin scoring is unaffected because a gin opponent has no deadwood and cannot lay off.

Index

Constants

View Source
const GinBonus = 25

GinBonus is added to the knocker's score for going gin.

View Source
const HandTarget = 100

HandTarget is the match target: the first player to reach this cumulative score wins the match. Callers own match bookkeeping; this package only scores a single hand.

View Source
const UndercutBonus = 25

UndercutBonus is added to the opponent's score when they undercut the knocker.

Variables

This section is empty.

Functions

func BestMelds

func BestMelds(hand []int) (deadwood int, melds [][]int, unmatched []int)

BestMelds partitions a hand so each card belongs to at most one meld, choosing the decomposition that minimises the total deadwood value of the unmatched cards. It returns that minimal deadwood, one optimal set of melds (each a slice of card indices), and the unmatched (deadwood) cards.

A card may be usable in either a set or a run but not both; the optimum is found by an exhaustive backtracking search over all candidate melds (hand sizes are <= 11, so this is cheap), memoised on the set of resolved cards.

func CanKnock

func CanKnock(hand []int) bool

CanKnock reports whether a 10-card hand may knock: its minimal deadwood is at most 10.

func CardString

func CardString(c int) string

CardString renders a card as rank+suit, e.g. "A♠", "T♦", "K♣", "5♥".

func Deadwood

func Deadwood(hand []int) int

Deadwood returns the minimal total deadwood value of a hand, i.e. the deadwood component of BestMelds.

func DeadwoodValue

func DeadwoodValue(c int) int

DeadwoodValue is the point value of a card as deadwood: Ace=1, 2..10 = pip value (rank+1), and J/Q/K = 10.

func IsGin

func IsGin(hand []int) bool

IsGin reports whether a hand is gin: it melds completely with zero deadwood.

func LayOff

func LayOff(deadwood []int, knockerMelds [][]int) (remaining int, laid []int)

LayOff models the defender's lay-off after a non-gin knock: the defender may place their own unmatched (deadwood) cards onto the knocker's melds, reducing the deadwood value counted against them.

deadwood is the defender's unmatched card indices (as returned by BestMelds); knockerMelds is the knocker's melds (also from BestMelds). A card may join a SET of its rank (a set caps at 4 cards) or extend either END of a RUN with a same-suit adjacent card. Run extensions cascade (extending 5♠6♠7♠ with 4♠ then admits 3♠), so lay-off iterates to a fixed point. Ace is low only, matching BestMelds: there is no Q-K-A wraparound.

It returns the remaining deadwood VALUE (the sum of DeadwoodValue over the cards NOT laid off) and the list of card indices that were laid off.

func Rank

func Rank(c int) int

Rank returns the rank of card c: 0=Ace, 1=2, .. 8=9, 9=10, 10=J, 11=Q, 12=K.

func Score

func Score(knocker, opponent []int, gin bool) (knockerPts, oppPts int)

Score computes the points for a single completed hand between the knocker and their opponent. gin indicates the knocker went gin (all cards melded).

Let kd = Deadwood(knocker). In the non-gin case od is the opponent's deadwood AFTER laying off onto the knocker's melds (see LayOff); for gin it is the opponent's full deadwood (a gin opponent cannot lay off).

  • Gin: knocker scores od + GinBonus; opponent scores 0.
  • Normal knock (kd < od): knocker scores od - kd; opponent scores 0.
  • Undercut (kd >= od, non-gin): opponent scores (kd - od) + UndercutBonus; knocker scores 0.

func Suit

func Suit(c int) int

Suit returns the suit of card c: 0=♠, 1=♥, 2=♦, 3=♣.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL