Implement drawing and running a turn.

This commit is contained in:
2023-04-01 20:52:46 -07:00
parent a6b2c92f86
commit 576a2dd69e
3 changed files with 166 additions and 9 deletions

View File

@ -1,6 +1,15 @@
package cardsim
import "math/rand"
import (
"errors"
"math/rand"
)
var (
ErrUncooperativeCards = errors.New("a milion cards refused to join the hand")
WarningStalemate = errors.New("no actions can be taken")
)
// Player stores all gameplay state for one player at a specific point in time.
// Game-specific data is stored in Stats.
@ -138,3 +147,111 @@ const (
func (g GameState) Over() bool {
return g == GameLost || g == GameWon || g == GameCrashed || g == GameStalled
}
// ChapterBreak apends a chapter break to p.TemporaryMessages, unless it is
// empty or the most recent non-nil message is already a chapter break.
func (p *Player[C]) ChapterBreak() {
for i := len(p.TemporaryMessages) - 1; i >= 0; i-- {
m := p.TemporaryMessages[i]
if IsSpecialMessage(m, ChapterBreak) {
return
}
if p.TemporaryMessages[i] != nil {
p.TemporaryMessages = append(p.TemporaryMessages, ChapterBreak)
return
}
}
// No non-nil messages -- nothing to do.
}
// SectionBreak apends a section break to p.TemporaryMessages, unless it is
// empty or the most recent non-nil message is already a section/chapter break.
func (p *Player[C]) SectionBreak() {
for i := len(p.TemporaryMessages) - 1; i >= 0; i-- {
m := p.TemporaryMessages[i]
if IsSpecialMessage(m, ChapterBreak) || IsSpecialMessage(m, SectionBreak) {
return
}
if m != nil {
p.TemporaryMessages = append(p.TemporaryMessages, SectionBreak)
return
}
}
// No non-nil messages -- nothing to do.
}
// Simulate executes the simulation up to the start of the next turn. If the
// simulation crashes, the game state becomes GameCrashed. This returns any
// generated errors; if the debugging mode is 0 or greater, they also become
// temporary messages for the next turn.
func (p *Player[C]) Simulate() error {
var errs ErrorCollector
p.TemporaryMessages = nil
p.TemporaryPanels = nil
errs.Add(p.Rules.Run(p))
errs.Add(p.StartNextTurn())
if errs.HasFailure() {
p.State = GameCrashed
}
if p.DebugLevel > 0 && !errs.IsEmpty() {
p.ChapterBreak()
p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%d errors and warnings:", len(errs.Errs)))
for i, e := range errs.Errs {
yikes := " "
if IsSeriousError(e) {
yikes = "!"
}
p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%s[%d]:\t%v", yikes, i, e))
}
}
return errs.Emit()
}
// StartNextTurn increments the turn counter, resets the action counter,
// and draws back up to full. If the player cannot take any actions after
// drawing is complete, the game stalls. (If a drawn card would like to
// force the player to take no actions for a turn, the best approach is to
// make that card Urgent and make it reset ActionsRemaining to 0 after it runs.)
func (p *Player[C]) StartNextTurn() error {
var errs ErrorCollector
p.TurnNumber++
p.ActionsRemaining = p.ActionsPerTurn
errs.Add(p.FillHand())
if len(p.Hand)+len(p.PermanentActions) == 0 {
p.State = GameStalled
errs.Add(Warningf("%w: no cards in hand, no permanent actions", WarningStalemate))
}
if p.ActionsRemaining == 0 {
p.State = GameStalled
errs.Add(Warningf("%w: 0 actions available in the turn", WarningStalemate))
}
return errs.Emit()
}
// FillHand draws up to the hand limit, informing cards that they have been
// drawn. If more than a million cards refuse to enter the hand, this crashes
// with ErrUncooperativeCards. If the deck does not have enough cards, this
// returns WarningTooFewCards.
func (p *Player[C]) FillHand() error {
failureLimit := 1000000
for failureLimit > 0 && p.Deck.Len() > 0 && len(p.Hand) < p.HandLimit {
c := p.Deck.Draw()
if c.Drawn(p) {
p.Hand = append(p.Hand, c)
} else {
failureLimit--
}
}
if len(p.Hand) >= p.HandLimit {
return nil
}
if failureLimit <= 0 {
return ErrUncooperativeCards
}
return WarningTooFewCards
}