CardSimEngine/cardsim/card.go

225 lines
7.3 KiB
Go

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
}),
}
}