From de7092cf4b147c7a395195781a7b90a1232a585c Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Sat, 8 Apr 2023 18:33:58 -0700 Subject: [PATCH] SwitchingCard.IsValid: control drawability. Cards can now specify conditions that must be met for them to be drawn into the hand. Additionally, this improves the documentation of SwitchingCard. --- koboldsim/cardtypes.go | 67 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/koboldsim/cardtypes.go b/koboldsim/cardtypes.go index c35f7b0..9a7b662 100644 --- a/koboldsim/cardtypes.go +++ b/koboldsim/cardtypes.go @@ -42,11 +42,56 @@ type Policy interface { // previously, and undoes it before doing a different one. It is always valid // to draw. type SwitchingCard struct { - Name cardsim.Message - Desc cardsim.Message - IsUrgent bool - After func(Card, *Player, CardOption) error - Policies []Policy + // Name contains the name of the card, displayed as its title in the + // action selection menu and in the card detail page itself. + Name cardsim.Message + + // Desc contains the event description for the card, displayed in the + // card detail page. + Desc cardsim.Message + + // IsUrgent marks a card as urgent. If the player has any urgent cards + // in hand, they cannot act on any non-urgent cards (or permanent actions + // not marked as urgent). + IsUrgent bool + + // After is invoked after the player has chosen to act on this card and + // the chosen option has been fully enacted. If the card should be returned + // to the deck, After is responsible for doing this! + // + // If After is not provided, ShuffleIntoBottomHalf is used as a fallback. + // + // The first argument to After is the card itself. This will be type + // *SwitchingCard. It's represented as Card so general "After" functions + // that can be used with multiple card types (for example, + // ShuffleIntoBottomHalf) can be trivially implemented. + // + // If the card cannot be drawn into the hand because it has an IsValid + // check and the check fails, After is invoked with a nil CardOption. + // ShuffleIntoBottomHalf works just fine with a nil argument here. + // (It doesn't care about the CardOption at all.) + After func(Card, *Player, CardOption) error + + // IsValid is used to check whether the card can be drawn into the hand. + // If it cannot, After is immediately invoked (whenever the card was + // being drawn, which is probably the "draw" stage of the turn but can + // happen any time if something else causes the player to draw cards) with + // a nil CardOption (because no option was selected) and the SwitchingCard + // does not invoke any option and does not change its active policy. + // + // The first argument to IsValid is the card itself. This will be type + // *SwitchingCard. It's presented via the Card interface to support + // general validity functions that could be used with arbitrary kinds of Card. + IsValid func(Card, *Player) bool + + // Policies contains the options the player may choose between. Policy is + // a more specific type than CardOption; a Policy can be un-enacted. + // Unenactment of the previous policy before selecting a new one is the + // core feature of SwitchingCard. + Policies []Policy + + // lastPolicy stores the last policy selected for this card. It's used + // extensively by SwitchingCard's logic. lastPolicy Policy // ShowUnavailable controls whether options for which Enabled() = false @@ -66,7 +111,12 @@ func (s *SwitchingCard) Urgent(*Player) bool { } // Drawn implements Card. -func (s *SwitchingCard) Drawn(*Player) bool { +func (s *SwitchingCard) Drawn(p *Player) bool { + if s.IsValid != nil && !s.IsValid(s, p) { + err := s.Then(p, nil) + p.ReportError(err) // can't do anything with the error right now + return false + } return true } @@ -118,6 +168,11 @@ func (s *SwitchingCard) Then(p *Player, o CardOption) error { return errs.Emit() } +// CurrentlyEnacted returns the currently enacted Policy, if any. +func (s *SwitchingCard) CurrentlyEnacted() Policy { + return s.lastPolicy +} + // BasicPolicy is a straightfoward implementation of Policy. If the currently // enacted option is re-enacted, it refunds the player's action point. type BasicPolicy struct {