Editorial illustration: A card is data, not just an image

A card is data, not just an image

The problem I needed to solve

When I started Dalo’s core, I wanted a card to retain its logical identity across Hokm and Joker. Changing its image should not create a new card in a hand or change an existing selection.

Implementation in this snapshot

CardInstance holds CardData and creates a separate InstanceId in its constructor. Suit and Rank come from the data; the ID belongs to the live instance rather than its image.

The design decision and its cost

This separation creates an additional object, but makes the distinction between a shared definition and a card inside a match explicit. Two cards displaying the same image are not necessarily the same instance.

Current boundaries

A fresh constructor-generated ID is not a complete match restoration solution. This class alone does not serialize hands or provide network synchronization.

Identity while moving a card

Consider a simple situation: a player selects a card while the interface sorts their hand. If selection stores only an array index, that index may identify a different card afterward. InstanceId provides a way to refer to the instance itself. CardInstance creates it with Guid.NewGuid, allowing a consumer to associate selection with a card and resolve its display position separately. This is an architectural use case, rather than a claim that every interface component follows that pattern.

Another boundary separates Data from CardInstance. Two cards may refer to one CardData definition while retaining different IDs. Suit, Rank and IsJoker are read from that definition; the instance does not make independent copies of those properties. This avoids disagreement between duplicated attributes, but requires careful management of the referenced data. A readonly data field prevents replacing the reference; it does not, by itself, make every property of the referenced object immutable elsewhere.

The constructor also rejects null with ArgumentNullException. For Dalo, this is useful because a card without a definition should fail before rendering or winner evaluation. However, the constructor has no parameter for restoring a previous ID. Saving a match or receiving a card over a network would require a separate policy for reconstructed identity. Creating another card with the same suit and rank does not restore the previous instance or automatically validate old references.

What this stage taught me

For me, card identity began at this boundary. As selection and animation became more involved, I could still refer to the same instance even when its place in the interface changed.

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