Compare commits
No commits in common. "b806264154aa8ebdb06c4051df65a19cdbb09ee4" and "d569e77fc9f8733273943692f088fc9f2d22dcf7" have entirely different histories.
b806264154
...
d569e77fc9
@ -10,19 +10,9 @@ type Card[C StatsCollection] interface {
|
||||
Title(p *Player[C]) (Message, error)
|
||||
|
||||
// Urgent reports whether the card is considered urgent. If
|
||||
// the player has any urgent cards in hand, they cannot choose to act
|
||||
// on a non-urgent card.
|
||||
// the player hasa any
|
||||
Urgent(p *Player[C]) bool
|
||||
|
||||
// Drawn is invoked after a card is drawn, before presenting it to the
|
||||
// player. If Drawn returns `false`, the card is discarded without being
|
||||
// put into the hand or shown to the player and a replacement is drawn
|
||||
// instead. To put a card back on the bottom of the deck (or similar)
|
||||
// use p.Deck.Insert (or a related function) to put it back explicitly
|
||||
// in the right position. Do not put it right back on top of the deck or
|
||||
// you'll create an infinite loop.
|
||||
Drawn(p *Player[C]) bool
|
||||
|
||||
// EventText returns the text to display on the card. If it returns an
|
||||
// error that is not a warning, the game crashes.
|
||||
EventText(p *Player[C]) (Message, error)
|
||||
@ -54,8 +44,7 @@ type CardOption[C StatsCollection] interface {
|
||||
Enact(p *Player[C]) (Message, error)
|
||||
}
|
||||
|
||||
// A BasicCard is a Card with fixed title, text, options, and optional
|
||||
// post-option callback. It never does anything in particular when drawn.
|
||||
// A BasicCard is a Card with fixed title, text, options, and optional post-option callback.
|
||||
type BasicCard[C StatsCollection] struct {
|
||||
CardTitle Message
|
||||
IsUrgent bool
|
||||
@ -87,10 +76,6 @@ func (b *BasicCard[C]) Then(p *Player[C], option CardOption[C]) error {
|
||||
return b.AfterOption(p, option)
|
||||
}
|
||||
|
||||
func (b *BasicCard[C]) Drawn(p *Player[C]) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// A BasicOption is a CardOption with fixed text, effects, and output.
|
||||
type BasicOption[C StatsCollection] struct {
|
||||
Text Message
|
||||
|
@ -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.
|
||||
@ -31,8 +31,8 @@ func (d *Deck[C]) Len() int {
|
||||
return len(d.cards)
|
||||
}
|
||||
|
||||
// Insert puts one or more cards at a specific location in the Deck. Cards
|
||||
// at that location and all locations after are shifted deeper into the deck.
|
||||
// Insert puts a card at a specific location in the Deck. The card previously
|
||||
// at that location and all locations after are shifted one card later.
|
||||
// Negative indexes are counted from the bottom of the deck. BottomOfDeck is
|
||||
// a sentinel value for the bottommost position; -1 is one card above.
|
||||
//
|
||||
@ -44,7 +44,7 @@ func (d *Deck[C]) Len() int {
|
||||
// WarningTopClamped. Like all warnings, these can be safely ignored and the
|
||||
// program is in a well-defined state, but you may want to check for them
|
||||
// if you expect some other behavior.
|
||||
func (d *Deck[C]) Insert(idx int, card ...Card[C]) error {
|
||||
func (d *Deck[C]) Insert(idx int, card Card[C]) error {
|
||||
var errs ErrorCollector
|
||||
// Calculate actual target index.
|
||||
switch {
|
||||
@ -60,7 +60,14 @@ func (d *Deck[C]) Insert(idx int, card ...Card[C]) error {
|
||||
idx += d.Len()
|
||||
}
|
||||
// remaining case: 0 <= idx <= d.Len(), which is a normal forward insert index.
|
||||
d.cards = InsertInto(d.cards, idx, card...)
|
||||
|
||||
// Place new card on bottom and "bubble" into position.
|
||||
// Takes O(N) time. If this turns out to be a problem, implement a more
|
||||
// efficient data structure.
|
||||
d.cards = append(d.cards, card)
|
||||
for i := len(d.cards) - 1; i > idx; i-- {
|
||||
d.cards[i], d.cards[i-1] = d.cards[i-1], d.cards[i]
|
||||
}
|
||||
return errs.Emit()
|
||||
}
|
||||
|
||||
@ -196,77 +203,3 @@ 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
|
||||
}
|
||||
|
||||
// ShuffleRange shuffles the cards between the specified fractions of
|
||||
// the deck; the top of the deck is 0.0 and the bottom of the deck is
|
||||
// 1.0. This rounds "outward" -- "partial" cards at each end are counted.
|
||||
// This can return the same warnings ShufflePart can in the same circumstances
|
||||
// and may also complain about a backwards range.
|
||||
func (d *Deck[C]) ShuffleRange(loFrac, hiFrac float64) error {
|
||||
var errs ErrorCollector
|
||||
if loFrac > hiFrac {
|
||||
errs.Add(Warningf("%w: %f > %f", WarningBackwardsRange, loFrac, hiFrac))
|
||||
loFrac, hiFrac = hiFrac, loFrac
|
||||
}
|
||||
low := int(math.Floor(loFrac * float64(d.Len())))
|
||||
high := int(math.Ceil(hiFrac * float64(d.Len())))
|
||||
n := 1 + high - low
|
||||
errs.Add(d.ShufflePart(low, n))
|
||||
return errs.Emit()
|
||||
}
|
||||
|
||||
// ShuffleTop uses ShuffleRange to shuffle the top frac (between 0.0 and 1.0)
|
||||
// of the deck. See ShuffleRange and ShufflePart for information on
|
||||
// rounding and warnings.
|
||||
func (d *Deck[C]) ShuffleTop(frac float64) error {
|
||||
return d.ShuffleRange(0.0, frac)
|
||||
}
|
||||
|
||||
// Shufflebottom uses ShuffleRange to shuffle the bottom frac (between 0.0 and
|
||||
// 1.0) of the deck. See ShuffleRange and ShufflePart for information on
|
||||
// rounding and warnings.
|
||||
func (d *Deck[C]) ShuffleBottom(frac float64) error {
|
||||
return d.ShuffleRange(frac, 1.0)
|
||||
}
|
||||
|
@ -7,27 +7,6 @@ import (
|
||||
)
|
||||
|
||||
// A Rule implements an operation run on every game turn.
|
||||
//
|
||||
// Rule[C] is a generic interface. Like any other generic type, it describes a
|
||||
// family of related types: each different kind of StatsCollection that Rule
|
||||
// could pertain to is the basis of a distinct type of Rule.
|
||||
//
|
||||
// When implementing a generic interface, you do not need to implement a
|
||||
// generic type. In the case of Rule, you are likely to be writing rules for a
|
||||
// specific simulation. That simulation will have some associated
|
||||
// StatsCollection type. The rules you write will only need to implement the
|
||||
// variation of Rule that pertains specifically to that type.
|
||||
//
|
||||
// For example, if your `StatsCollection` type is `KoboldMineData`, then rules
|
||||
// for the simulation referring to it would implement `Rule[KoboldMineData]`
|
||||
// only. So the `Enact` function you implment would take an argument of type
|
||||
// `*Player[KoboldMineData]`, not some undefined type `C` that could be any
|
||||
// StatsCollection. Since it takes a `*Player[KoboldMineData]` as an argument,
|
||||
// you then know that the player's `Stats` field is not just any
|
||||
// StatsCollection, it is KoboldMineData specifically. The compiler won't
|
||||
// require you to convert from "some `StatsCollection`" to "`KoboldMineData`
|
||||
// specifically" when using the `Player[KoboldMineData].Stats` field,
|
||||
// because the type of that field is already `KoboldMineData`.
|
||||
type Rule[C StatsCollection] interface {
|
||||
// Label is an internal name the rule can be recognized by.
|
||||
// Some things may be easier if it is unique, but it does not have to be.
|
||||
|
@ -1,128 +0,0 @@
|
||||
package cardsim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// InsertInto inserts one or more items into a slice at an arbitrary index.
|
||||
// Items already in the slice past the target position move to later positons.
|
||||
//
|
||||
// Like `append`, this may move the underlying array and it produces a new
|
||||
// slice header (under the hood, it uses `append`). It returns the new slice
|
||||
// (the original is in an undefined state and should no longer be used).
|
||||
//
|
||||
// If loc is negative or more than one past the end of T, Insert panics.
|
||||
func InsertInto[T any](slice []T, loc int, elements ...T) []T {
|
||||
if loc < 0 || loc > len(slice) {
|
||||
panic(fmt.Sprintf("can't Insert at location %d in %d-element slice", loc, len(slice)))
|
||||
}
|
||||
|
||||
// is this a no-op?
|
||||
if len(elements) == 0 {
|
||||
return slice
|
||||
}
|
||||
// is this just an append?
|
||||
if loc == len(slice) {
|
||||
return append(slice, elements...)
|
||||
}
|
||||
|
||||
offset := len(elements)
|
||||
oldLen := len(slice)
|
||||
newSize := oldLen + offset
|
||||
if newSize <= cap(slice) {
|
||||
// We can reslice in place.
|
||||
slice = slice[:newSize]
|
||||
|
||||
// Scoot trailing to their new positions.
|
||||
copy(slice[loc+offset:], slice[loc:oldLen])
|
||||
|
||||
// Insert the new elements.
|
||||
copy(slice[loc:], elements)
|
||||
|
||||
return slice
|
||||
}
|
||||
|
||||
// Reallocate. Do the normal thing of doubling the size as a minimum
|
||||
// when increasing space for a dynamic array; this amortizes the
|
||||
// cost of repeatedly reallocating and moving the slice.
|
||||
newCap := cap(slice) * 2
|
||||
if newCap < newSize {
|
||||
newCap = newSize
|
||||
}
|
||||
newSlice := make([]T, newSize, newCap)
|
||||
if loc > 0 {
|
||||
copy(newSlice, slice[0:loc])
|
||||
}
|
||||
copy(newSlice[loc:], elements)
|
||||
copy(newSlice[loc+offset:], slice[loc:])
|
||||
return newSlice
|
||||
}
|
||||
|
||||
// DeleteFrom deletes an item from a slice at an arbitrary index. Items after it
|
||||
// scoot up to close the gap. This returns the modified slice (like Append).
|
||||
//
|
||||
// If the provided location is not a valid location in the slice, this panics.
|
||||
func DeleteFrom[T any](slice []T, loc int) []T {
|
||||
return DeleteNFrom(slice, loc, 1)
|
||||
}
|
||||
|
||||
// DeleteNFrom deletes N items from a slice at an arbitrary index. Items after
|
||||
// it scoot up to close the gap. This returns the modified slice (like Append).
|
||||
//
|
||||
// If the range of items that would be deleted is not entirely valid within the
|
||||
// slice, this panics.
|
||||
func DeleteNFrom[T any](slice []T, loc, n int) []T {
|
||||
if loc < 0 || loc+n > len(slice) {
|
||||
panic(fmt.Sprintf("can't delete %d elements from a %d-element slice at location %d", n, len(slice), loc))
|
||||
}
|
||||
|
||||
// Easy cases.
|
||||
if n == 0 {
|
||||
return slice
|
||||
}
|
||||
if loc == 0 {
|
||||
return slice[n:]
|
||||
}
|
||||
if loc+n == len(slice) {
|
||||
return slice[0:loc]
|
||||
}
|
||||
|
||||
// Is it shorter to move up or move down?
|
||||
if len(slice)-loc-n > loc {
|
||||
// Move forward -- the end is big.
|
||||
copy(slice[n:], slice[:loc])
|
||||
return slice[n:]
|
||||
}
|
||||
// Move backward -- the beginnng is big or they're the same size
|
||||
// (and moving backwards preserves more usable append capacity later).
|
||||
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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user