Editorial illustration: From a deck definition to a live deck

From a deck definition to a live deck

The problem I needed to solve

A project’s deck definition should not be the same object as the deck players draw from. I needed a reusable definition and separate state for each match.

Implementation in this snapshot

RuntimeDeck validates DeckDefinition first. Reset creates fresh instances and clears DrawPile and DrawnCards; Draw moves the first card into the drawn collection.

The design decision and its cost

Draw throws explicitly, while TryDraw returns false for an empty deck. These paths let me separate an ordinary game condition from a violated programming contract.

Current boundaries

DrawMany checks availability before drawing, but removing from the front of a List shifts elements. This implementation does not establish that it is optimized for very large decks.

Restarting and tracking drawn cards

Suppose a hand ends and the player chooses to restart. RuntimeDeck.Reset does more than refill the remaining-card list: it clears DrawnCards and obtains fresh instances from DeckDefinition. Cards from the previous match should not contribute to the next match’s counts. Reset does not call Shuffle, however. The new order comes from the deck definition, and the caller still decides whether to shuffle. Restarting and randomizing remain independent operations.

Before its first draw, DrawMany checks that the amount is positive and RemainingCount is sufficient. Asking for ten cards when only nine remain fails before partially consuming the deck. That is easier to reason about than an incomplete deal. This precheck is not a general transaction, though: DrawMany still performs successive Draw calls and does not govern failures in a consumer that later adds the returned cards to a player’s hand.

DrawnCards can be a misleading name: it records cards leaving the deck, rather than their current locations on the table. A drawn card may later be in a hand, the table center or a collected pile; this class does not track those movements. IReadOnlyList provides read access but does not create a separate snapshot. Subsequent draws change the underlying list. A consumer needing a stable report for one moment must make the appropriate copy and preserve that report’s meaning.

What this stage taught me

For Dalo, the useful outcome is restarting from one stable definition. Remaining and drawn cards are observable without turning the deck asset itself into a particular match’s mutable state.

Explore Dalo

Latest devlogs

Editorial illustration: Pasur launch and telemetry in the shared structure

Pasur launch and telemetry in the shared structure

Adding Pasur needs both a correct mode handoff and valid public telemetry.

Read article
Editorial illustration: Presenting selection and captures in Pasur

Presenting selection and captures in Pasur

Pasur motion should explain which hand card was played and which table cards were captured.

Read article
Editorial illustration: Pasur bot decisions from capture options

Pasur bot decisions from capture options

My teammate’s Pasur bot chooses among legal options without inspecting hidden opposing cards.

Read article
Dalo