Compare commits

..

No commits in common. "2c1fc73ef57f2b7e2bfa7c959bdb5d4c99216dc9" and "8ce6539c8aea42f39da2fb91cf7d32b69b773d49" have entirely different histories.

View File

@ -9,14 +9,6 @@ import (
var ( var (
ErrOptionNotEnabled = errors.New("option not enabled") ErrOptionNotEnabled = errors.New("option not enabled")
ErrPolicyNotEnacted = errors.New("cannot unenact policy that is not enacted") ErrPolicyNotEnacted = errors.New("cannot unenact policy that is not enacted")
// ErrUnimplemented and ErrKeepMessaage are "non-errors". They are used
// as special signals that the result needs to be handled in a special way;
// VerbosePolicy uses these to decide when to use the Default instead.
// If these are returned in a context that does not know how to respond
// to them, then they're just errors.
ErrUnimplemented = errors.New("unimplemented policy element")
ErrKeepMessaage = errors.New("use the default behavior but this message")
) )
type Policy interface { type Policy interface {
@ -108,7 +100,6 @@ func (s *SwitchingCard) Then(p *Player, o CardOption) error {
// BasicPolicy is a straightfoward implementation of Policy. If the currently // BasicPolicy is a straightfoward implementation of Policy. If the currently
// enacted option is re-enacted, it refunds the player's action point. // enacted option is re-enacted, it refunds the player's action point.
type BasicPolicy struct { type BasicPolicy struct {
Desc cardsim.Message
UnenactedDesc cardsim.Message UnenactedDesc cardsim.Message
EnactedDesc cardsim.Message EnactedDesc cardsim.Message
NothingChanged cardsim.Message NothingChanged cardsim.Message
@ -133,22 +124,13 @@ func (b *BasicPolicy) LastEnacted(_ int, p Policy) {
// OptionText implements CardOption. // OptionText implements CardOption.
func (b *BasicPolicy) OptionText(*Player) (cardsim.Message, error) { func (b *BasicPolicy) OptionText(*Player) (cardsim.Message, error) {
if b.currentlyEnacted { if b.currentlyEnacted {
if b.EnactedDesc == nil {
return nil, ErrUnimplemented
}
return b.EnactedDesc, nil return b.EnactedDesc, nil
} }
if b.UnenactedDesc == nil {
return nil, ErrUnimplemented
}
return b.UnenactedDesc, nil return b.UnenactedDesc, nil
} }
// Enact implements CardOption. // Enact implements CardOption.
func (b *BasicPolicy) Enact(p *Player) (cardsim.Message, error) { func (b *BasicPolicy) Enact(p *Player) (cardsim.Message, error) {
if b.Do == nil {
return nil, ErrUnimplemented
}
if b.currentlyEnacted { if b.currentlyEnacted {
p.ActionsRemaining++ p.ActionsRemaining++
if b.NothingChanged == nil { if b.NothingChanged == nil {
@ -156,10 +138,10 @@ func (b *BasicPolicy) Enact(p *Player) (cardsim.Message, error) {
} }
return b.NothingChanged, nil return b.NothingChanged, nil
} }
if b.Enabled(p) { if b.CanDo != nil && !b.CanDo(p) {
return b.Do(p)
}
return nil, ErrOptionNotEnabled return nil, ErrOptionNotEnabled
}
return b.Do(p)
} }
// Unenact implements Policy. // Unenact implements Policy.
@ -167,9 +149,6 @@ func (b *BasicPolicy) Unenact(p *Player) error {
if !b.currentlyEnacted { if !b.currentlyEnacted {
return ErrPolicyNotEnacted return ErrPolicyNotEnacted
} }
if b.Undo == nil {
return ErrUnimplemented
}
return b.Undo(p) return b.Undo(p)
} }
@ -179,7 +158,7 @@ func (b *BasicPolicy) Enabled(p *Player) bool {
return true return true
} }
if b.CanDo == nil { if b.CanDo == nil {
panic(ErrUnimplemented) b.CanDo = YesWeCan
} }
return b.CanDo(p) return b.CanDo(p)
} }
@ -191,209 +170,50 @@ func (b *BasicPolicy) Is(p Policy) bool {
return false return false
} }
// A VerbosePolicy is a group of related policies pretending to all be the same // A DescResuilt is descriptive text for an option and the text result of
// policy. Which policy is used is determined by what the previous policy for // enacting that option when it was described this way.
// the card was (as reported via a call to LastEnacted): type DescResult struct {
// Desc cardsim.Message
// * If no policy has yet been enacted, use FirstTime. Result cardsim.Message
// * If a policy has been enacted, use the Policy at the slot in Variants }
// that corresponds to the slot (on the Card) of the currently-enacted policy.
// * If the policy retrieved in this way returns ErrUnimplemented, throw away // A VerbosePolicy is an extension to a BasicPolicy. It emits the BasicPolicy's
// its response and use Default instead. For Enabled, which does not have // `UnenactedDesc` and the message returned from Do only when no policy has ever
// an error component to its return value, look for ErrUnimplemented as the // been enacted for this card; otherwise, it looks up the description and result
// argument to a Panic call, instead. // from a slice using the index of the last policy selected.
// * If the policy retrieved in this way returns ErrKeepMessage when Enact
// is called, it calls Default for the side effects but ignores its message,
// retaining the message from the original call. This is to avoid having to
// repeat the same Enact function except with different text each time.
// OptionText does this too, even though OptionText doesn't have side effects,
// so the same helper function can create a "constant message" callback
// that works the same for both fields so someone implementing a card won't
// accidentally fail to enact their policy's effects by using the wrong one
// in the wrong slot.
type VerbosePolicy struct { type VerbosePolicy struct {
Default Policy *BasicPolicy
FirstTime Policy
lastIdx int lastIdx int
lastWasMe bool Content []DescResult
lastEnacted Policy
Variants []Policy
} }
func (v *VerbosePolicy) LastEnacted(i int, p Policy) { func (v *VerbosePolicy) LastEnacted(i int, p Policy) {
v.lastIdx = i v.lastIdx = i
v.lastEnacted = p v.BasicPolicy.currentlyEnacted = v.Is(p)
v.lastWasMe = v.Is(p)
// make sure we can just assume that there is a policy in this slot,
// inserting the default if there is none.
v.fillDefaults()
// Tell the potential candidate policy about this, too. Two special cases:
// * first time -- use first-time policy
// * lastWasMe -- tell the polcy that the last encated policy was itself,
// since it doesn't know it's wrapped in another policy and would not
// recognize itself as v
if i < 0 {
v.FirstTime.LastEnacted(i, p) // p should be nil here...
} else if v.lastWasMe {
v.Variants[i].LastEnacted(i, v.Variants[i])
} else {
v.Variants[i].LastEnacted(i, p)
}
// In case we need it, also prepare the Default for use.
if v.lastWasMe {
v.Default.LastEnacted(i, v.Default)
} else {
v.Default.LastEnacted(i, p)
}
} }
func (v *VerbosePolicy) fillDefaults() { func (v *VerbosePolicy) OptionText(*Player) (cardsim.Message, error) {
if v.FirstTime == nil {
v.FirstTime = v.Default
}
for len(v.Variants) <= v.lastIdx {
v.Variants = append(v.Variants, v.Default)
}
if v.Variants[v.lastIdx] == nil {
v.Variants[v.lastIdx] = v.Default
}
}
func (v *VerbosePolicy) OptionText(p *Player) (cardsim.Message, error) {
var msg cardsim.Message
var err error
if v.lastIdx < 0 { if v.lastIdx < 0 {
msg, err = v.FirstTime.OptionText(p) return v.BasicPolicy.UnenactedDesc, nil
} else {
msg, err = v.Variants[v.lastIdx].OptionText(p)
} }
if errors.Is(err, ErrUnimplemented) { return v.Content[v.lastIdx].Desc, nil
msg, err = v.Default.OptionText(p)
}
return msg, err
} }
func (v *VerbosePolicy) Enact(p *Player) (cardsim.Message, error) { func (v *VerbosePolicy) Enact(p *Player) (cardsim.Message, error) {
var msg cardsim.Message msg, err := v.BasicPolicy.Enact(p)
var err error if v.lastIdx >= 0 {
if v.lastIdx < 0 { msg = v.Content[v.lastIdx].Result
msg, err = v.FirstTime.Enact(p)
} else {
msg, err = v.Variants[v.lastIdx].Enact(p)
}
if errors.Is(err, ErrUnimplemented) {
msg, err = v.Default.Enact(p)
} }
return msg, err return msg, err
} }
func (v *VerbosePolicy) Unenact(p *Player) error {
if !v.lastWasMe {
return ErrPolicyNotEnacted
}
var err error
if v.lastIdx < 0 {
err = v.FirstTime.Unenact(p)
} else {
err = v.Variants[v.lastIdx].Unenact(p)
}
if errors.Is(err, ErrUnimplemented) {
err = v.Default.Unenact(p)
}
return err
}
func (v *VerbosePolicy) Is(p Policy) bool { func (v *VerbosePolicy) Is(p Policy) bool {
if o, ok := p.(*VerbosePolicy); ok { if o, ok := p.(*VerbosePolicy); ok {
return o == v return o == p
} }
return false return false
} }
func (v *VerbosePolicy) Enabled(p *Player) (result bool) {
// oops, enablement isn't designed to error out. so we have to use
// panic/recover for this.
defer func() {
if x := recover(); x != nil {
if e, ok := x.(error); ok {
if errors.Is(e, ErrUnimplemented) {
// Recover and use the Default to cover for the missing
// Enabled method.
result = v.Default.Enabled(p)
return
}
}
// Whatever we caught, it's not something we're actually ready to recover from.
panic(x)
}
}()
if v.lastIdx < 0 {
result = v.FirstTime.Enabled(p)
return
}
result = v.Variants[v.lastIdx].Enabled(p)
return
}
// FuncPolicy implements Policy by calling specified functions. If they're
// missing, it returns ErrUnimplemented. It handles Is itself. It also tracks
// LastEnacted data (last index, which policy, is policy self) itself.
type FuncPolicy struct {
OptionTextFunc func(*Player) (cardsim.Message, error)
EnactFunc func(*Player) (cardsim.Message, error)
EnabledFunc func(*Player) bool
UnenactFunc func(*Player) error
// These three fields are assigned by LastEnacted and typically should not
// be set in the initializer.
LastEnactedIdx int
LastEnactedPolicy Policy
WasEnactedLast bool
}
func (f *FuncPolicy) OptionText(p *Player) (cardsim.Message, error) {
if f.OptionTextFunc == nil {
return nil, ErrUnimplemented
}
return f.OptionTextFunc(p)
}
func (f *FuncPolicy) Enact(p *Player) (cardsim.Message, error) {
if f.EnactFunc == nil {
return nil, ErrUnimplemented
}
return f.EnactFunc(p)
}
func (f *FuncPolicy) Enabled(p *Player) bool {
if f.EnabledFunc == nil {
panic(ErrUnimplemented)
}
return f.EnabledFunc(p)
}
func (f *FuncPolicy) Unenact(p *Player) error {
if f.UnenactFunc == nil {
return ErrUnimplemented
}
return f.UnenactFunc(p)
}
func (f *FuncPolicy) LastEnacted(i int, p Policy) {
f.LastEnactedIdx = i
f.LastEnactedPolicy = p
f.WasEnactedLast = f.Is(p)
}
func (f *FuncPolicy) Is(p Policy) bool {
fp, ok := p.(*FuncPolicy)
return ok && (f == fp)
}
// ShuffleIntoBottomHalf is a common "what to do with the card after?" behavior. // ShuffleIntoBottomHalf is a common "what to do with the card after?" behavior.
func ShuffleIntoBottomHalf(c Card, p *Player, _ CardOption) error { func ShuffleIntoBottomHalf(c Card, p *Player, _ CardOption) error {
p.Deck.InsertRandomBottom(0.5, c) p.Deck.InsertRandomBottom(0.5, c)