From b8c0e5603a0c0f18b847487a63064f7e210bba40 Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Sat, 1 Apr 2023 19:13:42 -0700 Subject: [PATCH] Add a general shuffler and deck shuffling. --- cardsim/deck.go | 44 +++++++++++++++++++++++++++++++++++++++++++- cardsim/sliceutil.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/cardsim/deck.go b/cardsim/deck.go index e1fa3a4..cca7381 100644 --- a/cardsim/deck.go +++ b/cardsim/deck.go @@ -23,7 +23,7 @@ const ( // The Deck stores cards yet-to-be-dealt. type Deck[C StatsCollection] struct { cards []Card[C] - rand rand.Rand + rand *rand.Rand } // Len returns the number of cards in the Deck. @@ -196,3 +196,45 @@ func (d *Deck[C]) InsertRandomRange(loFrac, hiFrac float64, card Card[C]) error errs.Add(d.Insert(slot, card)) return errs.Emit() } + +// Shuffle completely shuffles the deck. If the deck has one or fewer cards, +// this returns WarningTooFewCards since nothing can be shuffled. +func (d *Deck[C]) Shuffle() error { + if len(d.cards) < 2 { + return WarningTooFewCards + } + ShuffleAll(d.cards, d.rand) + return nil +} + +// ShufflePart shuffles the `n` cards of the deck starting at `loc`. +// If the provided range doesn't fit in the deck, this returns +// WarningTopClamped and/or WarningBottomClamped. If the eventual range +// of cards to be shuffled (after any off-the-end issues are corrected) +// is one or less, this returns WarningTooFewCards since nothing can +// be shuffled. +func (d *Deck[C]) ShufflePart(loc, n int) error { + if n < 2 { + // Nothing to do. + return WarningTooFewCards + } + + var errs ErrorCollector + if loc < 0 { + errs.Add(Warningf("%w: loc was %d", WarningTopClamped, loc)) + loc = 0 + } + if loc+n > d.Len() { + errs.Add(Warningf("%w: deck size %d does not have %d cards at and after location %d", + WarningBottomClamped, len(d.cards), n, loc)) + n = d.Len() - loc + // Now is there anything to do? + if n < 2 { + errs.Add(WarningTooFewCards) + return errs.Emit() + } + } + + ShufflePart(d.cards, d.rand, loc, n) + return nil +} diff --git a/cardsim/sliceutil.go b/cardsim/sliceutil.go index 1c3e881..e64207e 100644 --- a/cardsim/sliceutil.go +++ b/cardsim/sliceutil.go @@ -2,6 +2,7 @@ package cardsim import ( "fmt" + "math/rand" ) // InsertInto inserts one or more items into a slice at an arbitrary index. @@ -98,3 +99,30 @@ func DeleteNFrom[T any](slice []T, loc, n int) []T { copy(slice[loc:], slice[loc+n:]) return slice[:len(slice)-n] } + +// ShuffleAll shuffles everything in slice, using the provided rand.Rand. +// If no rand.Rand is provided, this uses the default source. +func ShuffleAll[T any](slice []T, r *rand.Rand) { + shuffle := rand.Shuffle + if r != nil { + shuffle = r.Shuffle + } + shuffle(len(slice), func(i, j int) { + slice[i], slice[j] = slice[j], slice[i] + }) +} + +// ShufflePart shuffles the `n` elements of `slice` starting at `loc` +// in-place, using the provided rand.Rand. If the range of items to +// shuffle is not entirely within `slice`, this panics. +// +// If no rand.Rand is provided, this uses the default source. +func ShufflePart[T any](slice []T, r *rand.Rand, loc, n int) { + if loc < 0 || loc+n > len(slice) { + panic(fmt.Sprintf("can't shuffle %d elements from a %d-element slice at location %d", n, len(slice), loc)) + } + if n < 1 { + return + } + ShuffleAll(slice[loc:loc+n], r) +}