package cardsim // A Card represents an option available to the player. Its methods may be // called many times per turn as the player considers their options. type Card[C StatsCollection] interface { // Title is the short name of the card displayed in the hand // and at the top of the card output. It receives the current // player as an argument. Title(p *Player[C]) Message // 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. 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) // Options returns the possible actions the player can take for this card. // There must be at least one option. Options(p *Player[C]) ([]CardOption[C], error) // Then is invoked after an option is selected and executed. The selected // option is provided as an argument. This allows cards to do certain // cleanup for every action -- for example, returning to the deck. Then(p *Player[C], option CardOption[C]) error } // A CardOption represents a choice a player could make for some card. type CardOption[C StatsCollection] interface { // OptionText returns the text displayed for this option. It may be called // many times within a turn as the player considers their options. If it // returns an error that is not a warning, the game crashes. OptionText(p *Player[C]) (Message, error) // Enact is called exactly once if the player chooses the option. It is // expected to update values in `p`. It returns the text displayed to the // player as a result of their action. If it returns an error that is not // a warning, the game crashes. // // After an option is enacted, the card is deleted. If a card should be // repeatable, Enact must return it to the deck (on every option) or // the card needs to reinsert itself with its Then function. Enact(p *Player[C]) (Message, error) // Enabled returns whether this option can curently be enacted. Enabled(p *Player[C]) bool } // A BasicCard is a Card with fixed title, text, options, and optional // post-option callback. It never does anything in particular when drawn. type BasicCard[C StatsCollection] struct { CardTitle Message IsUrgent bool CardText Message CardOptions []CardOption[C] // AfterOption is given the card itself as its first argument. AfterOption func(c Card[C], p *Player[C], option CardOption[C]) error } // Title implements Card. func (b *BasicCard[C]) Title(_ *Player[C]) Message { return b.CardTitle } // Urgent implements Card. func (b *BasicCard[C]) Urgent(_ *Player[C]) bool { return b.IsUrgent } // EventText implements Card. func (b *BasicCard[C]) EventText(_ *Player[C]) (Message, error) { return b.CardText, nil } // Options implements Card. func (b *BasicCard[C]) Options(_ *Player[C]) ([]CardOption[C], error) { return b.CardOptions, nil } // Then implements Card. func (b *BasicCard[C]) Then(p *Player[C], option CardOption[C]) error { if b.AfterOption == nil { return nil } return b.AfterOption(b, p, option) } // Drawn implements Card. func (b *BasicCard[C]) Drawn(_ *Player[C]) bool { return true } // RefundAction returns a func that can be used as an AfterOption, which returns // the player's action point. func RefundAction[C StatsCollection]() func(c Card[C], p *Player[C], option CardOption[C]) error { return func(c Card[C], p *Player[C], option CardOption[C]) error { p.ActionsRemaining++ return nil } } // A PanelCard is a Card that takes its title and text from an InfoPanel, // while options, urgency, and the post-option callback are specified // (like a BasicCard). It never does anything in particular when drawn. // // Omitting all options yields an inactionable card, which can be displayed // but not played. This can be useful for adding an info panel as a debug action. type PanelCard[C StatsCollection] struct { Panel InfoPanel[C] IsUrgent bool CardOptions []CardOption[C] // AfterOption is given the card itself as its first argument. AfterOption func(c Card[C], p *Player[C], option CardOption[C]) error } // Title implements Card. func (c *PanelCard[C]) Title(p *Player[C]) Message { return c.Panel.Title(p) } // Urgent implements Card. func (c *PanelCard[C]) Urgent(_ *Player[C]) bool { return c.IsUrgent } // EventText implements Card. func (c *PanelCard[C]) EventText(p *Player[C]) (Message, error) { msgs, err := c.Panel.Info(p) return MultiMessage(msgs), err } // Options implements Card. func (c *PanelCard[C]) Options(_ *Player[C]) ([]CardOption[C], error) { return c.CardOptions, nil } // Then implements Card. func (c *PanelCard[C]) Then(p *Player[C], option CardOption[C]) error { if c.AfterOption == nil { return nil } return c.AfterOption(c, p, option) } // Drawn implements Card. func (c *PanelCard[C]) Drawn(_ *Player[C]) bool { return true } // A BasicOption is a CardOption with fixed text, effects, and output. // It's always enabled. type BasicOption[C StatsCollection] struct { Text Message Effect func(*Player[C]) error Output Message } // OptionText implements CardOption. func (b *BasicOption[C]) OptionText(p *Player[C]) (Message, error) { return b.Text, nil } // Enact implements CardOption. func (b *BasicOption[C]) Enact(p *Player[C]) (Message, error) { return b.Output, b.Effect(p) } // Enabled implements CardOption. func (b *BasicOption[C]) Enabled(p *Player[C]) bool { return true } // OptionFunc attaches a fixed prompt to an Enact-like function. Unlike // BasicOption, the enactment function provided to OptionFunc returns // the text that should be output as a result of the action, so it is // possible to dynamically generate this text. func OptionFunc[C StatsCollection](text Message, f func(*Player[C]) (Message, error)) CardOption[C] { return &optionFunc[C]{text, f} } type optionFunc[C StatsCollection] struct { text Message f func(*Player[C]) (Message, error) } // OptionText implements CardOption. func (o *optionFunc[C]) OptionText(p *Player[C]) (Message, error) { return o.text, nil } // Enact implements CardOption. func (o *optionFunc[C]) Enact(p *Player[C]) (Message, error) { return o.f(p) } // Enabled implements CardOption. func (o *optionFunc[C]) Enabled(p *Player[C]) bool { return true } // OnlyDiscardFree returns a []CardOption[C] providing a single option, which // returns the action point. It does not shuffle the card back into the deck // or draw a replacement (consider the AfterFunc for that if needed). This // can be used for cards that are displayable but not actionable, but show up // as cards rather than permanent or debug actions for some reason. func OnlyDiscardFree[C StatsCollection](msg Message) []CardOption[C] { return []CardOption[C]{ OptionFunc(msg, func(p *Player[C]) (Message, error) { p.ActionsRemaining++ return MsgStr("Okay."), nil }), } }