Skip to content
izarmaPublic

About

A minimal, fast, deterministic bitmask deck of cards.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

33 Commits

Folders and files

Repository files navigation

bitdeck

bitdeck is a fixed-capacity (N ≤ 128) Bitmask. [Deck<N>] stores subset membership as a bitmask — u64 by default (up to 64 cards), or u128 (up to 128 cards) — enabling deterministic subset queries, bulk mutations, and (with the rand feature) uniform random draws without replacement and non-destructive peeks.

While it includes a standard 54-card deck preset ([Standard]) behind the cards feature, [Deck<N>] is completely generic and can be used for loot tables, turn based action queues, shuffle bags etc.

Features

  • rand (default): enables the random draw/peek APIs, including the *_mask bulk helpers.
  • alloc (default): enables the draw/peek *_into helpers that fill an alloc::vec::Vec. Requires rand.
  • serde: transparent bitmask serialization for [Deck<N>] (the backing mask type M is serialized transparently).
  • bevy: derives Component and Reflect for [Deck<N>], so it can be attached to entities, or wrapped in a Resource.
  • cards: exposes the cards module with the [Standard] deck newtype and its typed subsets — suits, ranks, colors, jokers, and predefined subsets like [Color::Red] and [FaceCards].

The crate is no_std. The default feature set includes alloc; disable default features and enable only the features you need for a no_std environment without an allocator.

Properties

  • Uniform without replacement. Every remaining item is equally likely on every draw; drawn items leave the deck. Use multiple copies of each item for weighted randomness.
  • Subset-aware. Draw from or query any subset (eg: a heart, a red card, a common drop) with a plain bitmask of type M, or use typed subsets scoped to a specific deck.
  • Typed subsets. The [deck!] macro generates a deck newtype together with classification enums and fixed unit-struct subsets that implement [Subset]. Pass Suit::Hearts or Jokers directly to draw_subset, count_subset, etc.
  • Const mask algebra. Classification enums expose const mask(), from_id(), and ALL; compose them with |, &, and ! in const contexts.
  • Bring your own RNG. All randomness comes from a caller-supplied rand RNG; the deck itself holds no RNG state.

Typed subsets

Define a deck and its classification enums together with [deck!]. Each variant maps to a bitmask, and the generated newtype accepts any impl Subset<_> in its *_subset methods:

use bitdeck::{deck, Deck};
use rand::{SeedableRng, rngs::SmallRng};

deck! {
    struct Loot = Deck<6>;
    subsets {
        enum Rarity { Common, Rare }
        from_id = |id: u8| id / 3;
        cards = 6;
    }
}

let mut rng = SmallRng::seed_from_u64(42);
let mut pool = Loot::default();

let rare = pool.draw_subset(&mut rng, Rarity::Rare).unwrap();
assert!(rare >= 3);
assert_eq!(pool.count_subset(Rarity::Common), 3);

Raw bitmask APIs (typed by the deck's mask type M) are still available through Deref.

Bitmask operations

[Deck<N>] is just a bitmask of type M (u64 by default, u128 for larger decks). Every operation below is a thin wrapper around a bitwise read or mutation on that mask.

  • Bitwise subset queries and bulk mutations are O(1).
  • Random draws/peeks are uniform without replacement. Selecting a card is O(1) on x86_64 with BMI2 — for u64 and u128 masks alike — and O(log 64) with the portable fallback elsewhere. See Randomness for the RNG values each bulk helper consumes.

POPCNT and BMI2 are detected via CPUID on first use and used when available, so no flags are needed on capable CPUs. To skip detection and require the instructions at runtime, enable them at compile time:

RUSTFLAGS="-C target-feature=+bmi2,+popcnt" cargo build

Note that this produces a binary that requires POPCNT and BMI2 at runtime; the default portable build uses them only when the CPU advertises support.

Randomness

Draws and peeks are uniform without replacement and consume only the RNG you pass in. A given RNG state and call sequence always produce the same result; what varies between helpers is how many RNG values a bulk call consumes.

The bulk helpers ([Deck::draw_mask], [Deck::draw_in_mask], [Deck::peek_mask], [Deck::peek_in_mask], and their *_into variants) consume values as follows, where available is the number of selected cards still in the deck:

Requested count RNG values consumed
count == 0 none
0 < count <= available / 2 count
available / 2 < count < available available - count (the complement is sampled)
count >= available none (every available card is returned)

The returned mask is always a uniform random subset of size min(count, available), so only the RNG position differs: for count > available / 2 a seeded run does not line up with a naive "draw count times" loop, and later draws from the same RNG follow from that position. Single-card [Deck::draw], [Deck::draw_in], [Deck::peek], and [Deck::peek_in] always consume exactly one value, and non-random operations consume none.

About

A minimal, fast, deterministic bitmask deck of cards.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages