From c30aca1f314471fc5af871ee7c045bd9ddbe638e Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Sat, 15 Apr 2023 20:59:21 -0700 Subject: [PATCH] Better error management. * "Uncooperative cards" is now a warning. * Cards and actions get "Then" invoked before the card processor considers erroring out. * Terminal UI: Errors and warnings from actions are displayed during the response; they're not only added to the temporary messages now. --- cardsim/messages.go | 13 ++++++++++ cardsim/player.go | 58 ++++++++++++++++++------------------------- cardsim/terminalui.go | 4 +++ 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/cardsim/messages.go b/cardsim/messages.go index 32345fe..0b98eec 100644 --- a/cardsim/messages.go +++ b/cardsim/messages.go @@ -35,6 +35,19 @@ func Msgf(f string, args ...any) Message { return stringMessage(fmt.Sprintf(f, args...)) } +// ErrorMessage returns a Message representing an Error. +// This is preferred over Msgf for errors, since future versions of the library +// may perform special message formatting for errors. +func ErrorMessage(e error) Message { + if e == nil { + return nil + } + if IsSeriousError(e) { + return MultiMessage{MsgStr("SERIOUS ERROR:"), Msgf("%v", e)} + } + return MultiMessage{MsgStr("Warning:"), Msgf("%v", e)} +} + // A SpecialMessage is a specific, uniquely identifiable message. type SpecialMessage struct { msg Message diff --git a/cardsim/player.go b/cardsim/player.go index 0722c73..c2df215 100644 --- a/cardsim/player.go +++ b/cardsim/player.go @@ -8,14 +8,14 @@ import ( ) var ( - ErrUncooperativeCards = errors.New("a milion cards refused to join the hand") - ErrInvalidCard = errors.New("invalid card specified") - ErrInvalidChoice = errors.New("invalid choice specified") - ErrNotUrgent = errors.New("action not urgent when urgent card is available") - ErrNoActions = errors.New("no actions remaining") - ErrNotDebugging = errors.New("this is a debug-only feature and you're not in debug mode") + ErrInvalidCard = errors.New("invalid card specified") + ErrInvalidChoice = errors.New("invalid choice specified") + ErrNotUrgent = errors.New("action not urgent when urgent card is available") + ErrNoActions = errors.New("no actions remaining") + ErrNotDebugging = errors.New("this is a debug-only feature and you're not in debug mode") - WarningStalemate = errors.New("no actions can be taken") + WarningStalemate = &Warning{errors.New("no actions can be taken")} + WarningUncoperativeCards = &Warning{errors.New("a milion cards refused to join the hand")} ) // Player stores all gameplay state for one player at a specific point in time. @@ -272,9 +272,9 @@ func (p *Player[C]) StartNextTurn() error { } // Draw draws a card into the hand, informing the card that it has 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. +// If more than a million cards refuse to enter the hand, this gives up and +// returns WarningUncooperativeCards. If the deck does not have enough cards, +// this returns WarningTooFewCards. func (p *Player[C]) Draw() error { for attempts := 0; attempts < 1000000; attempts++ { if p.Deck.Len() == 0 { @@ -286,13 +286,13 @@ func (p *Player[C]) Draw() error { return nil } } - return ErrUncooperativeCards + return WarningUncoperativeCards } // 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. +// drawn. If more than a million cards refuse to enter the hand, this gives up +// and returns WarningUncooperativeCards. If the deck does not have enough +// cards, this returns WarningTooFewCards. func (p *Player[C]) FillHand() error { var lastErr error for p.Deck.Len() > 0 && len(p.Hand) < p.HandLimit { @@ -386,17 +386,14 @@ func (p *Player[C]) EnactCardUnchecked(cardIdx, choiceIdx int) (Message, error) ret, err := options[choiceIdx].Enact(p) errs.Add(err) - if IsSeriousError(err) { - p.State = GameCrashed - return ret, errs.Emit() - } - err = card.Then(p, options[choiceIdx]) errs.Add(err) + + err = errs.Emit() if IsSeriousError(err) { p.State = GameCrashed } - return ret, errs.Emit() + return ret, err } // EnactCard executes a card choice, removes it from the hand, and decrements @@ -470,17 +467,13 @@ func (p *Player[C]) enactActionUnchecked(actionSource []Card[C], actionIdx, choi ret, err := chosen.Enact(p) errs.Add(err) - if IsSeriousError(err) { - p.State = GameCrashed - return ret, errs.Emit() - } - err = card.Then(p, chosen) errs.Add(err) - if IsSeriousError(err) { + retErr := errs.Emit() + if IsSeriousError(retErr) { p.State = GameCrashed } - return ret, errs.Emit() + return ret, retErr } // EnactPermanentAction executes a permanently-available card and decrements @@ -512,15 +505,11 @@ func (p *Player[C]) ReportError(e error) { if e == nil || p.DebugLevel < -1 { return } - if p.DebugLevel < 0 && !IsSeriousError(e) { - return - } - p.ChapterBreak() - severity := "[Warning]" + minLvl := NotDebugging if IsSeriousError(e) { - severity = "[ERROR]" + minLvl = HideWarnings } - p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%s: %v", severity, e)) + p.Debug(minLvl, ErrorMessage(e)) } // CanAct returns whether the player has actions theoretically available. @@ -534,6 +523,7 @@ func (p *Player[C]) Debug(minLevel int, msg Message) { if p.DebugLevel < minLevel || msg == nil { return } + p.ChapterBreak() p.TemporaryMessages = append(p.TemporaryMessages, msg) } diff --git a/cardsim/terminalui.go b/cardsim/terminalui.go index 166abfc..6d8a378 100644 --- a/cardsim/terminalui.go +++ b/cardsim/terminalui.go @@ -46,6 +46,10 @@ func RunSimpleTerminalUI[C StatsCollection](p *Player[C]) error { } continue } + if err != nil { + display(ErrorMessage(err)) + display(MsgStr("")) + } display(msg) wait() }