@theresamclaird/playing-cards
v0.1.2
Published
Zero-dependency TypeScript library for modeling playing cards: decks, multi-deck shoes, shuffle, cut, deal, and more.
Maintainers
Readme
@theresamclaird/playing-cards
A zero-dependency TypeScript toolkit for modeling playing cards: build decks and multi-deck shoes, then shuffle, cut, draw, and deal. A pure functional core (immutable, tree-shakeable, framework-agnostic) with an optional stateful Shoe class for game code. Ships ESM, CJS, and type definitions.
Install
npm install @theresamclaird/playing-cardsQuick start
Functional API (immutable)
Every function returns new arrays and mutates nothing, so results drop straight into React state.
import { createDeck, shuffle, cut, deal, createRng } from "@theresamclaird/playing-cards";
const deck = createDeck(); // standard 52-card deck
const rng = createRng(42); // seeded → reproducible
const shuffled = cut(shuffle(deck, rng), { fraction: 0.5 });
const { hands, rest } = deal(shuffled, { hands: 4, perHand: 5 });
// hands: Card[][] (4 hands of 5), rest: the undealt remainderStateful Shoe class
import { Shoe } from "@theresamclaird/playing-cards";
const shoe = new Shoe({ decks: 6, cutCard: 52 }); // casino blackjack shoe
shoe.shuffle().cut();
shoe.burn(1); // burn a card
const player = shoe.draw(2);
const dealer = shoe.draw(2);
shoe.discard(player).discard(dealer);
if (shoe.needsReshuffle) shoe.reshuffle();Cards
A Card is a plain object:
interface Card {
rank: string; // "A", "2".."10", "J", "Q", "K", or "JOKER"
suit: string | null; // "S" | "H" | "D" | "C"; null for jokers
id: string; // stable, unique — safe as a React key
deck: number; // source deck index (0 for a single deck)
}id is unique even across duplicate cards: the first ace of spades is "AS", the next (another deck, or a Pinochle double) is "AS#1". Compare on rank/suit; never parse id.
Deck specs
Build any deck with a DeckSpec ({ ranks, suits, jokers? }). The order of ranks defines strength, low to high. Presets included:
STANDARD_52— A,2..10,J,Q,K × four suitsEUCHRE_24— 9,10,J,Q,K,A × four suitsPINOCHLE_48— 9,J,Q,K,10,A doubled × four suits
import { createDeck, STANDARD_52 } from "@theresamclaird/playing-cards";
const withJokers = createDeck({ ...STANDARD_52, jokers: 2 }); // 54 cardsRandomness
createRng(seed?) returns a seedable generator (mulberry32). The same seed always produces the same sequence — ideal for tests, replays, and deterministic multiplayer. Every randomized function accepts an optional Rng; the default is Math.random. For cryptographic randomness, pass your own crypto.getRandomValues-backed function.
API summary
| Function | Description |
| --- | --- |
| createDeck(spec?) | One ordered deck (default STANDARD_52). |
| createShoe(deckCount, spec?) | Several decks combined, with unique ids. |
| shuffle(cards, rng?) | Unbiased Fisher–Yates shuffle. |
| cut(cards, opts) | Cut by position, fraction, or random range. |
| draw(cards, n?) | Draw n from the top; clamps on over-draw. |
| deal(cards, { hands, perHand, mode? }) | Deal round-robin (default) or sequential; clamps. |
| rankValue / compareCards / sortHand | Rank strength and sorting helpers. |
| createRng(seed?) | Seedable RNG. |
| Shoe | Stateful pile with discard, reshuffle, burn, peek, cut card. |
Over-draw policy
Asking for more cards than remain returns what's left rather than throwing — running low is normal game flow. Genuinely invalid arguments (negative counts, fraction outside 0–1, NaN) throw a RangeError.
License
MIT © Theresa McLaird
