222 lines
5.6 KiB
Go
222 lines
5.6 KiB
Go
package koboldsim
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"git.chromaticdragon.app/kistaro/CardSimEngine/cardsim"
|
|
)
|
|
|
|
var (
|
|
ErrOptionNotEnabled = errors.New("option not enabled")
|
|
ErrPolicyNotEnacted = errors.New("cannot unenact policy that is not enacted")
|
|
)
|
|
|
|
type Policy interface {
|
|
cardsim.CardOption[*KoboldMine]
|
|
// Unenact reverses the previous enactment of this policy.
|
|
Unenact(p *Player) error
|
|
|
|
// LastEnacted informs this Policy which choice was last enacted and
|
|
// which index it is under; this allows it to prepare to describe
|
|
// itself differently depending on the last selected policy. If no
|
|
// option has ever been chosen for this card, the index is -1.
|
|
//
|
|
// The Card that would present this Policy as an option must use this,
|
|
// and provide the active policy that this is a candidate to replace,
|
|
LastEnacted(int, Policy)
|
|
|
|
// Is returns whether this policy is this other policy. This is a strict
|
|
// identity equality check, don't do anything clever here.
|
|
Is(Policy) bool
|
|
}
|
|
|
|
// A SwitchingCard is an issue card that remembers which option was selected
|
|
// 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
|
|
lastPolicy Policy
|
|
}
|
|
|
|
// Title implements Card.
|
|
func (s *SwitchingCard) Title(*Player) cardsim.Message {
|
|
return s.Name
|
|
}
|
|
|
|
// Urgent implements Card.
|
|
func (s *SwitchingCard) Urgent(*Player) bool {
|
|
return s.IsUrgent
|
|
}
|
|
|
|
// Drawn implements Card.
|
|
func (s *SwitchingCard) Drawn(*Player) bool {
|
|
return true
|
|
}
|
|
|
|
// EventText implements Card.
|
|
func (s *SwitchingCard) EventText(*Player) (cardsim.Message, error) {
|
|
return s.Desc, nil
|
|
}
|
|
|
|
// Options implements Card.
|
|
func (s *SwitchingCard) Options(*Player) ([]CardOption, error) {
|
|
lastIdx := -1
|
|
for i, p := range s.Policies {
|
|
if p.Is(s.lastPolicy) {
|
|
lastIdx = i
|
|
break
|
|
}
|
|
}
|
|
ret := make([]CardOption, len(s.Policies))
|
|
for i, p := range s.Policies {
|
|
p.LastEnacted(lastIdx, s.lastPolicy)
|
|
ret[i] = p
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// Then implements Card.
|
|
func (s *SwitchingCard) Then(p *Player, o CardOption) error {
|
|
newPolicy := o.(Policy)
|
|
var errs cardsim.ErrorCollector
|
|
if s.lastPolicy != nil && !newPolicy.Is(s.lastPolicy) {
|
|
err := s.lastPolicy.Unenact(p)
|
|
if cardsim.IsSeriousError(err) {
|
|
return err
|
|
}
|
|
errs.Add(err)
|
|
}
|
|
s.lastPolicy = o.(Policy)
|
|
if s.After != nil {
|
|
errs.Add(s.After(s, p, o))
|
|
}
|
|
return errs.Emit()
|
|
}
|
|
|
|
// 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 {
|
|
UnenactedDesc cardsim.Message
|
|
EnactedDesc cardsim.Message
|
|
NothingChanged cardsim.Message
|
|
Do func(*Player) (cardsim.Message, error)
|
|
Undo func(*Player) error
|
|
CanDo func(*Player) bool
|
|
|
|
currentlyEnacted bool
|
|
}
|
|
|
|
// YesWeCan returns true. It's the default value for BasicPolicy.CanDo / BasicPolicy.CanUndo.
|
|
func YesWeCan(*Player) bool {
|
|
return true
|
|
}
|
|
|
|
// LastEnacted notifies b about the last-enacted policy in its group. It updates
|
|
// b.currentlyEnacted accordingly.
|
|
func (b *BasicPolicy) LastEnacted(_ int, p Policy) {
|
|
b.currentlyEnacted = b.Is(p)
|
|
}
|
|
|
|
// OptionText implements CardOption.
|
|
func (b *BasicPolicy) OptionText(*Player) (cardsim.Message, error) {
|
|
if b.currentlyEnacted {
|
|
return b.EnactedDesc, nil
|
|
}
|
|
return b.UnenactedDesc, nil
|
|
}
|
|
|
|
// Enact implements CardOption.
|
|
func (b *BasicPolicy) Enact(p *Player) (cardsim.Message, error) {
|
|
if b.currentlyEnacted {
|
|
p.ActionsRemaining++
|
|
if b.NothingChanged == nil {
|
|
b.NothingChanged = cardsim.MsgStr("You continue your current approach.")
|
|
}
|
|
return b.NothingChanged, nil
|
|
}
|
|
if b.CanDo != nil && !b.CanDo(p) {
|
|
return nil, ErrOptionNotEnabled
|
|
}
|
|
return b.Do(p)
|
|
}
|
|
|
|
// Unenact implements Policy.
|
|
func (b *BasicPolicy) Unenact(p *Player) error {
|
|
if !b.currentlyEnacted {
|
|
return ErrPolicyNotEnacted
|
|
}
|
|
return b.Undo(p)
|
|
}
|
|
|
|
// Enabled implements CardOption.
|
|
func (b *BasicPolicy) Enabled(p *Player) bool {
|
|
if b.currentlyEnacted {
|
|
return true
|
|
}
|
|
if b.CanDo == nil {
|
|
b.CanDo = YesWeCan
|
|
}
|
|
return b.CanDo(p)
|
|
}
|
|
|
|
func (b *BasicPolicy) Is(p Policy) bool {
|
|
if o, ok := p.(*BasicPolicy); ok {
|
|
return o == b
|
|
}
|
|
return false
|
|
}
|
|
|
|
// A DescResuilt is descriptive text for an option and the text result of
|
|
// enacting that option when it was described this way.
|
|
type DescResult struct {
|
|
Desc cardsim.Message
|
|
Result cardsim.Message
|
|
}
|
|
|
|
// A VerbosePolicy is an extension to a BasicPolicy. It emits the BasicPolicy's
|
|
// `UnenactedDesc` and the message returned from Do only when no policy has ever
|
|
// been enacted for this card; otherwise, it looks up the description and result
|
|
// from a slice using the index of the last policy selected.
|
|
type VerbosePolicy struct {
|
|
*BasicPolicy
|
|
lastIdx int
|
|
Content []DescResult
|
|
}
|
|
|
|
func (v *VerbosePolicy) LastEnacted(i int, p Policy) {
|
|
v.lastIdx = i
|
|
v.BasicPolicy.currentlyEnacted = v.Is(p)
|
|
}
|
|
|
|
func (v *VerbosePolicy) OptionText(*Player) (cardsim.Message, error) {
|
|
if v.lastIdx < 0 {
|
|
return v.BasicPolicy.UnenactedDesc, nil
|
|
}
|
|
return v.Content[v.lastIdx].Desc, nil
|
|
}
|
|
|
|
func (v *VerbosePolicy) Enact(p *Player) (cardsim.Message, error) {
|
|
msg, err := v.BasicPolicy.Enact(p)
|
|
if v.lastIdx >= 0 {
|
|
msg = v.Content[v.lastIdx].Result
|
|
}
|
|
return msg, err
|
|
}
|
|
|
|
func (v *VerbosePolicy) Is(p Policy) bool {
|
|
if o, ok := p.(*VerbosePolicy); ok {
|
|
return o == p
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ShuffleIntoBottomHalf is a common "what to do with the card after?" behavior.
|
|
func ShuffleIntoBottomHalf(c Card, p *Player, _ CardOption) error {
|
|
p.Deck.InsertRandomBottom(0.5, c)
|
|
return nil
|
|
}
|