Compare commits

12 Commits

Author SHA1 Message Date
1eedfb50e8 Fix enablement 2023-04-03 20:33:35 -07:00
c2d637b109 Fix out-of-range check. 2023-04-03 20:25:29 -07:00
7313ac0d73 Fully implement Festival of Bureaucracy. 2023-04-03 20:24:01 -07:00
140d7b6cbb DisabledPolicy: a policy that doesn't 2023-04-03 20:12:06 -07:00
5af762474c OverrideDefaultMsg and support in VerbosePolicy
For when you need a partially-functional message but don't want to repeatedly write the same Enact func.
2023-04-03 19:57:09 -07:00
2c1fc73ef5 FuncPolicy: function pointer policy
For when you don't want to go to the trouble of writing a type, but do need actual functions.
2023-04-03 19:52:47 -07:00
a1f55c865d Refactor VerbosePolicy.
This allows it to switch between other complete policies, with fallbacks to a default if parts aren't impelemented. Complementing it is the change to BasicPolicy, which throws ErrUnimplemented if fields are missing, which VerbosePolicy uses as a "go ask the default" sign.
2023-04-03 19:41:32 -07:00
8ce6539c8a IncompleteImplementationofSecondIssue 2023-04-03 16:18:26 -07:00
0e77206868 Change "Productivity" to "Income" and "Expense
Also fixes a typo and an incorrectly assigned variable.
2023-04-03 14:35:36 -07:00
4e983bd0f0 Fix nil deref and identity failure.
Also updates deps to pick up a message display fix.
2023-04-03 01:59:23 -07:00
c798ba31e1 Fix nil dereference. 2023-04-03 01:40:47 -07:00
47fa7c3d6b tidy go.mod 2023-04-03 01:38:30 -07:00
6 changed files with 427 additions and 102 deletions

2
go.mod
View File

@ -2,4 +2,4 @@ module git.chromaticdragon.app/kistaro/KoboldSim
go 1.20
require git.chromaticdragon.app/kistaro/CardSimEngine v0.1.1
require git.chromaticdragon.app/kistaro/CardSimEngine v0.1.3

8
go.sum
View File

@ -1,4 +1,4 @@
git.chromaticdragon.app/kistaro/CardSimEngine v0.1.0 h1:ZBByQW64MwyOloLGgQHMHgp4Vug6fj9ERdg8c+OEqBk=
git.chromaticdragon.app/kistaro/CardSimEngine v0.1.0/go.mod h1:y511g1vH+CXntY0FauKYU7Y4gQy+jlkNzEFOws9Yxvg=
git.chromaticdragon.app/kistaro/CardSimEngine v0.1.1 h1:nPp8ymzfnwzrzfzjkuvDAb4X73BUgUNBGKHhDp6Iz3c=
git.chromaticdragon.app/kistaro/CardSimEngine v0.1.1/go.mod h1:VFaOagdbtM6gH87ioHent8v76nDh9PddpymMqWdrLfI=
git.chromaticdragon.app/kistaro/CardSimEngine v0.1.2 h1:+8KVFhSxXbQO7CPzmL89sJ+qjgU4J42Z5OinGuDMR0U=
git.chromaticdragon.app/kistaro/CardSimEngine v0.1.2/go.mod h1:VFaOagdbtM6gH87ioHent8v76nDh9PddpymMqWdrLfI=
git.chromaticdragon.app/kistaro/CardSimEngine v0.1.3 h1:rNaDDXnPVoMpavpx4vR9k30sl0nO0BnCe32nZbAF2IM=
git.chromaticdragon.app/kistaro/CardSimEngine v0.1.3/go.mod h1:VFaOagdbtM6gH87ioHent8v76nDh9PddpymMqWdrLfI=

View File

@ -13,51 +13,54 @@ var cards = []Card{
EnactedDesc: cardsim.MsgStr("[current policy] Your war chief is presntly monitoring the situation, building up your military, and securing your creches."),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value += 100
p.Stats.SectorScavengingProductivity.Value += 0.01
p.Stats.GovWarProductivity.Value += 0.02
p.Stats.SectorScavengingIncome.Value += 0.01
p.Stats.GovWarExpense.Value += 0.02
return cardsim.MsgStr("Kobolds are known to be born warriors."), nil
},
Undo: func(p *Player) error {
p.Stats.Kobolds.Value -= 100
p.Stats.SectorScavengingProductivity.Value -= 0.01
p.Stats.GovWarProductivity.Value -= 0.02
p.Stats.SectorScavengingIncome.Value -= 0.01
p.Stats.GovWarExpense.Value -= 0.02
return nil
},
CanDo: YesWeCan,
},
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr(`Your head miner considers the matter worriedly. "Creches under military control? No. That would invite chaos. We need to dig deeper; we can have a peaceful, orderly society if we just get far enough away from surfacers."`),
EnactedDesc: cardsim.MsgStr("[current policy] Your head miner is presently leading a project to dig as far away from the surface as possible."),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value += 40
p.Stats.SectorMiningProductivity.Value += 0.02
p.Stats.GovBureaucracyProductivity.Value += 0.01
p.Stats.SectorMiningIncome.Value += 0.02
p.Stats.GovBureaucracyExpense.Value += 0.01
return cardsim.MsgStr("Kobolds are known to be cowards hiding in the dark."), nil
},
Undo: func(p *Player) error {
p.Stats.Kobolds.Value -= 40
p.Stats.SectorMiningProductivity.Value -= 0.02
p.Stats.GovBureaucracyProductivity.Value -= 0.01
p.Stats.SectorMiningIncome.Value -= 0.02
p.Stats.GovBureaucracyExpense.Value -= 0.01
return nil
},
CanDo: YesWeCan,
},
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr(`Your nursery director is incensed. "Creches under military control? Never! Let young kobolds play! In fact, cut the military just for suggesting this. The threats facing us are completely overstated."`),
EnactedDesc: cardsim.MsgStr("[current policy] Military funding has been diverted into early childhood education."),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value -= 40
p.Stats.SectorScavengingProductivity.Value -= 0.01
p.Stats.GovWarProductivity.Value -= 0.02
p.Stats.SectorScavengingIncome.Value -= 0.01
p.Stats.GovWarExpense.Value -= 0.02
return cardsim.MsgStr("An undefended hunting outpost near the surface was recently wiped out by a raid."), nil
},
Undo: func(p *Player) error {
p.Stats.Kobolds.Value += 40
p.Stats.SectorScavengingProductivity.Value += 0.01
p.Stats.GovWarProductivity.Value += 0.02
p.Stats.SectorScavengingIncome.Value += 0.01
p.Stats.GovWarExpense.Value += 0.02
return nil
},
CanDo: YesWeCan,
},
&VerbosePolicy{
BasicPolicy: &BasicPolicy{
Default: &BasicPolicy{
UnenactedDesc: cardsim.MsgStr("This isn't about a disaster and can probably be safely dismissed."),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value += 20
@ -67,28 +70,135 @@ var cards = []Card{
p.Stats.Kobolds.Value -= 20
return nil
},
CanDo: YesWeCan,
},
Content: []DescResult{
{
Desc: cardsim.MsgStr("Rejecting this issue will also reject the military's natalist stance."),
Result: cardsim.MsgStr("Militant natalism has been reduced by policy."),
Variants: []Policy{
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr("Rejecting this issue will also reject the military's natalist stance."),
Do: OverrideMsg(cardsim.MsgStr("Militant natalism has been reduced by policy.")),
},
{
Desc: cardsim.MsgStr(`"Dig deeper" pressures in your nation may be excessive.`),
Result: cardsim.MsgStr("Some of the lower depths are being abandoned."),
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr(`"Dig deeper" pressures in your nation may be excessive.`),
Do: OverrideMsg(cardsim.MsgStr("Some of the lower depths are being abandoned.")),
},
{
Desc: cardsim.MsgStr("Near-surface patrols may need to be increased."),
Result: cardsim.MsgStr("More and better-armed hunting outpsts are being established."),
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr("Near-surface patrols may need to be increased."),
Do: OverrideMsg(cardsim.MsgStr("More and better-armed hunting outposts are being established.")),
},
{
Desc: cardsim.MsgStr("This isn't about a disaster and can probably continue to be safely dismissed."),
Result: cardsim.MsgStr("Creche control doesn't shift that easily."),
&BasicPolicy{
EnactedDesc: cardsim.MsgStr("This isn't about a disaster and can probably continue to be safely dismissed."),
Do: OverrideMsg(cardsim.MsgStr("Creche control doesn't shift that easily.")),
},
},
},
}, // end of "Warborn" policies
}, // end of "Warborn" card
&SwitchingCard{
Name: cardsim.MsgStr("International Festival of Bureaucracy"),
Desc: cardsim.MsgStr(" Good times are upon us! A great festival has been declared between many kobold nations, celebrating the orderly nature of the kobold soul! That's right, it's the International Festival of Bureaucracy!"),
After: ShuffleIntoBottomHalf,
Policies: []Policy{
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr(`Your Minister of Administration is practically jumping for joy. "This is our opportunity to prove that we're a prosperous society and attract some fresh blood! We need to raise salaries in the bureaucracy, grant some time off to our bureaucrats to make sure they can attend, and try to hire talent away from other nations. We'll forge productive trade relations by this, you'll see!`),
EnactedDesc: cardsim.MsgStr("[current policy] Your bureaucrats are really looking forward to attending, where they can boast about how the festival boosted their salaries."),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value += 80
p.Stats.SectorScavengingIncome.Value += 0.01
p.Stats.GovBureaucracyExpense.Value += 0.03
return cardsim.MsgStr("Bureaucrats are considered pillars of society."), nil
},
Undo: func(p *Player) error {
p.Stats.Kobolds.Value -= 80
p.Stats.SectorScavengingIncome.Value -= 0.01
p.Stats.GovBureaucracyExpense.Value -= 0.03
return nil
},
CanDo: YesWeCan,
},
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr(`Your Minister of Education pulls you aside. "This is a good opportunity to head-hunt. Bureaucrats from many nations will be attending. There's got to be some who'll work for less than the ones we've got. There are some terribly threatened or impoverished communities among kobolds.`),
EnactedDesc: cardsim.MsgStr("Your nation's policy is clear: the festival of bureaucracy is a chance to trade workers with other kobold nations in your attempts to build a more efficient government."),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value += 20
p.Stats.GovBureaucracyExpense.Value -= 0.01
return cardsim.MsgStr("Immigrant bureaucrats complain about being under-appreciated."), nil
},
Undo: func(p *Player) error {
p.Stats.Kobolds.Value -= 20
p.Stats.GovBureaucracyExpense.Value += 0.01
return nil
},
CanDo: YesWeCan,
// TODO(rakeela): This option should only be available if the fourth option isn't the status quo and bureaucratic expense is at least 0.02.
},
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr(`Your Minister of Education greets you exuberantly. "We've trimmed the wages of the bureaucracy beautifully, but maybe we can scout some outright volunteers. Some nations are absolute disasters, after all.`),
EnactedDesc: cardsim.MsgStr(`It's hard to find volunteer workers at the festival, but your nation is doing its best to equip the bureaucracy with exactly that.`),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value += 10
p.Stats.GovBureaucracyExpense.Value -= 0.02
return cardsim.MsgStr("The local bureaucracy is staffed by volunteer labor."), nil
},
Undo: func(p *Player) error {
p.Stats.Kobolds.Value -= 10
p.Stats.GovBureaucracyExpense.Value += 0.02
return nil
},
CanDo: YesWeCan,
// TODO(rakeela): This option should only be available if the fourth option isn't the status quo and bureaucratic expense is less than 0.02.
},
&BasicPolicy{
UnenactedDesc: cardsim.MsgStr(`One of your non-bureaucrat friends has been in a foul temper. "This festival is everything that's wrong with our society. Life isn't about filling out forms. We ought to snub this festival and outright fire some bureaucrats."`),
EnactedDesc: cardsim.MsgStr("Your nation currently bans the International Festival of Bureaucracy. There will be no local celebration without a policy change."),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value -= 80
p.Stats.GovBureaucracyExpense.Value -= 0.04
return cardsim.MsgStr("A wave of bureaucrats just emigrated along with kobolds incensed by the nation's lack of respect for administration."), nil
},
Undo: func(p *Player) error {
p.Stats.Kobolds.Value += 80
p.Stats.GovBureaucracyExpense.Value += 0.04
return nil
},
CanDo: YesWeCan,
},
&VerbosePolicy{
Default: &BasicPolicy{
UnenactedDesc: cardsim.MsgStr("Some kobolds will attend the festival on their own, and we may face desertions for our lack of interest."),
EnactedDesc: cardsim.MsgStr("Some kobolds will attend the festival on their own. No change is expected."),
Do: func(p *Player) (cardsim.Message, error) {
p.Stats.Kobolds.Value -= 20
return cardsim.MsgStr("A festival of bureaucracy lured away a few kobolds to other nations."), nil
},
Undo: func(p *Player) error {
p.Stats.Kobolds.Value += 20
return nil
},
CanDo: YesWeCan,
},
Variants: []Policy{
nil,
&DisabledPolicy{cardsim.MsgStr("TODO(rakeela): write")},
&DisabledPolicy{cardsim.MsgStr("TODO(rakeela): write")},
&FuncPolicy{
OptionTextFunc: func(p *Player) (cardsim.Message, error) {
if p.Stats.GovBureaucracyExpense.Value >= -0.03 {
return cardsim.MsgStr("Permitting the festival will yield some immigration, but we'll be expected to rebuild our bureaucracy."), nil
}
return cardsim.MsgStr("Permitting the festival will yield some immigration."), nil
},
EnactFunc: func(p *Player) (cardsim.Message, error) {
if p.Stats.GovBureaucracyExpense.Value >= -0.03 {
return cardsim.MsgStr("A festival of bureaucracy just saw the nation's bureaucracy rebuilt."), ErrKeepMessage
}
return cardsim.MsgStr("A festival of bureaucracy brought in immigrants wondering at the nation's lack thereof."), ErrKeepMessage
},
},
nil, // remember, if using a BasicPolicy, this uses EnactedDesc
},
},
},
},
} // end of card list
func initDeck(d *cardsim.Deck[*KoboldMine]) {

View File

@ -9,6 +9,14 @@ import (
var (
ErrOptionNotEnabled = errors.New("option not enabled")
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")
ErrKeepMessage = errors.New("use the default behavior but this message")
)
type Policy interface {
@ -83,7 +91,7 @@ func (s *SwitchingCard) Options(*Player) ([]CardOption, error) {
func (s *SwitchingCard) Then(p *Player, o CardOption) error {
newPolicy := o.(Policy)
var errs cardsim.ErrorCollector
if !newPolicy.Is(s.lastPolicy) {
if s.lastPolicy != nil && !newPolicy.Is(s.lastPolicy) {
err := s.lastPolicy.Unenact(p)
if cardsim.IsSeriousError(err) {
return err
@ -100,6 +108,7 @@ func (s *SwitchingCard) Then(p *Player, o CardOption) error {
// 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 {
Desc cardsim.Message
UnenactedDesc cardsim.Message
EnactedDesc cardsim.Message
NothingChanged cardsim.Message
@ -115,30 +124,31 @@ func YesWeCan(*Player) bool {
return true
}
// lastEnacted notifies b about the last-enacted policy in its group. It updates
// b.currentlyEnacted accordingly and returns itself.
//
// It also installs placeholders if CanDo or CanUndo is unspecified.
// 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 = false
if o, ok := p.(*BasicPolicy); ok && o == b {
b.currentlyEnacted = true
}
if b.CanDo == nil {
b.CanDo = YesWeCan
}
b.currentlyEnacted = b.Is(p)
}
// OptionText implements CardOption.
func (b *BasicPolicy) OptionText(*Player) (cardsim.Message, error) {
if b.currentlyEnacted {
if b.EnactedDesc == nil {
return nil, ErrUnimplemented
}
return b.EnactedDesc, nil
}
if b.UnenactedDesc == nil {
return nil, ErrUnimplemented
}
return b.UnenactedDesc, nil
}
// Enact implements CardOption.
func (b *BasicPolicy) Enact(p *Player) (cardsim.Message, error) {
if b.Do == nil {
return nil, ErrUnimplemented
}
if b.currentlyEnacted {
p.ActionsRemaining++
if b.NothingChanged == nil {
@ -146,10 +156,10 @@ func (b *BasicPolicy) Enact(p *Player) (cardsim.Message, error) {
}
return b.NothingChanged, nil
}
if !b.CanDo(p) {
return nil, ErrOptionNotEnabled
}
if b.Enabled(p) {
return b.Do(p)
}
return nil, ErrOptionNotEnabled
}
// Unenact implements Policy.
@ -157,6 +167,9 @@ func (b *BasicPolicy) Unenact(p *Player) error {
if !b.currentlyEnacted {
return ErrPolicyNotEnacted
}
if b.Undo == nil {
return ErrUnimplemented
}
return b.Undo(p)
}
@ -166,7 +179,7 @@ func (b *BasicPolicy) Enabled(p *Player) bool {
return true
}
if b.CanDo == nil {
b.CanDo = YesWeCan
panic(ErrUnimplemented)
}
return b.CanDo(p)
}
@ -178,52 +191,254 @@ func (b *BasicPolicy) Is(p Policy) bool {
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.
// A VerbosePolicy is a group of related policies pretending to all be the same
// policy. Which policy is used is determined by what the previous policy for
// the card was (as reported via a call to LastEnacted):
//
// * If no policy has yet been enacted, use FirstTime.
// * 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
// its response and use Default instead. For Enabled, which does not have
// an error component to its return value, look for ErrUnimplemented as the
// argument to a Panic call, instead.
// * 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 {
*BasicPolicy
Default Policy
FirstTime Policy
lastIdx int
Content []DescResult
lastWasMe bool
lastEnacted Policy
Variants []Policy
}
func (v *VerbosePolicy) LastEnacted(i int, p Policy) {
v.lastIdx = i
v.BasicPolicy.LastEnacted(i, p)
}
v.lastEnacted = p
v.lastWasMe = v.Is(p)
func (v *VerbosePolicy) OptionText(*Player) (cardsim.Message, error) {
if v.lastIdx < 0 {
return v.BasicPolicy.UnenactedDesc, nil
// 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)
}
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
func (v *VerbosePolicy) fillDefaults() {
if v.FirstTime == nil {
v.FirstTime = v.Default
}
for len(v.Variants) <= v.lastIdx {
v.Variants = append(v.Variants, v.Default)
}
if v.lastIdx > 0 && 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 {
msg, err = v.FirstTime.OptionText(p)
} else {
msg, err = v.Variants[v.lastIdx].OptionText(p)
}
if errors.Is(err, ErrKeepMessage) {
_, err = v.Default.OptionText(p)
} else if errors.Is(err, ErrUnimplemented) {
msg, err = v.Default.OptionText(p)
}
return msg, err
}
func (v *VerbosePolicy) Enact(p *Player) (cardsim.Message, error) {
var msg cardsim.Message
var err error
if v.lastIdx < 0 {
msg, err = v.FirstTime.Enact(p)
} else {
msg, err = v.Variants[v.lastIdx].Enact(p)
}
if errors.Is(err, ErrKeepMessage) {
_, err = v.Default.Enact(p)
} else if errors.Is(err, ErrUnimplemented) {
msg, err = v.Default.Enact(p)
}
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 {
if o, ok := p.(*VerbosePolicy); ok {
return o == p
return o == v
}
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)
}
// A DisabledPolicy is never enabled.
type DisabledPolicy struct {
Msg cardsim.Message
}
func (d *DisabledPolicy) OptionText(p *Player) (cardsim.Message, error) {
return d.Msg, nil
}
func (d *DisabledPolicy) Enact(*Player) (cardsim.Message, error) {
return nil, ErrOptionNotEnabled
}
func (d *DisabledPolicy) Enabled(*Player) bool {
return false
}
func (d *DisabledPolicy) Unenact(*Player) error {
return ErrPolicyNotEnacted
}
func (d *DisabledPolicy) LastEnacted(int, Policy) {}
func (d *DisabledPolicy) Is(p Policy) bool {
dp, ok := p.(*DisabledPolicy)
return ok && (dp == d)
}
// 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
}
// OverrideDefaultMsg returns a closure that returns the provided message and
// ErrKeepMessage. This can be used in a FuncPolicy to tell a VerbosePolicy
// to use its Default but keep the message written here, to avoid writing
// repetitive Enact funcs (and, for that matter, OptionText funcs, even though
// the underlying Default call should be unnecessary).
func OverrideMsg(m cardsim.Message) func(*Player) (cardsim.Message, error) {
return func(p *Player) (cardsim.Message, error) {
return m, ErrKeepMessage
}
}

View File

@ -19,8 +19,8 @@ func InitPlayer() *Player {
Intro: cardsim.MsgStr("We await your command, Overlord."),
Filter: cardsim.VisibleOrDebugStatsNamed[*KoboldMine](
"Kobolds",
"Total Sector Productivity",
"Total Government Productivity",
"Total Sector Income",
"Total Government Expense",
),
}
p.State = cardsim.GameActive

View File

@ -8,11 +8,11 @@ import (
type KoboldMine struct {
Kobolds cardsim.Stored[int64]
SectorMiningProductivity cardsim.Stored[float64]
SectorScavengingProductivity cardsim.Stored[float64]
SectorMiningIncome cardsim.Stored[float64]
SectorScavengingIncome cardsim.Stored[float64]
GovBureaucracyProductivity cardsim.Stored[float64]
GovWarProductivity cardsim.Stored[float64]
GovBureaucracyExpense cardsim.Stored[float64]
GovWarExpense cardsim.Stored[float64]
}
func (k *KoboldMine) ProductivityFunc(s *cardsim.Stored[float64]) func() float64 {
@ -21,40 +21,40 @@ func (k *KoboldMine) ProductivityFunc(s *cardsim.Stored[float64]) func() float64
}
}
func (k *KoboldMine) TotalSectorProductivity() float64 {
return float64(k.Kobolds.Value) * (k.SectorMiningProductivity.Value + k.SectorScavengingProductivity.Value)
func (k *KoboldMine) TotalSectorIncome() float64 {
return float64(k.Kobolds.Value) * (k.SectorMiningIncome.Value + k.SectorScavengingIncome.Value)
}
func (k *KoboldMine) TotalGovProductivity() float64 {
return float64(k.Kobolds.Value) * (k.GovBureaucracyProductivity.Value + k.GovWarProductivity.Value)
func (k *KoboldMine) TotalGovExpense() float64 {
return float64(k.Kobolds.Value) * (k.GovBureaucracyExpense.Value + k.GovWarExpense.Value)
}
func (k *KoboldMine) Stats() []cardsim.Stat {
stats := cardsim.ExtractStats(k)
funcs := []cardsim.Stat{
cardsim.StatFunc(
"Total Sector Mining Productivity",
k.ProductivityFunc(&k.SectorMiningProductivity),
"Total Sector Mining Income",
k.ProductivityFunc(&k.SectorMiningIncome),
),
cardsim.StatFunc(
"Total Sector Scavenging Productivity",
k.ProductivityFunc(&k.SectorScavengingProductivity),
"Total Sector Scavenging Income",
k.ProductivityFunc(&k.SectorScavengingIncome),
),
cardsim.StatFunc(
"Total Government Bureaucracy Productivity",
k.ProductivityFunc(&k.GovBureaucracyProductivity),
"Total Government Bureaucracy Expense",
k.ProductivityFunc(&k.GovBureaucracyExpense),
),
cardsim.StatFunc(
"Total Government War Productivity",
k.ProductivityFunc(&k.GovBureaucracyProductivity),
"Total Government War Expense",
k.ProductivityFunc(&k.GovWarExpense),
),
cardsim.StatFunc(
"Total Sector Productivity",
k.TotalSectorProductivity,
"Total Sector Income",
k.TotalSectorIncome,
),
cardsim.StatFunc(
"Total Government Productivity",
k.TotalGovProductivity,
"Total Government Expense",
k.TotalGovExpense,
),
}
stats = append(stats, funcs...)
@ -68,20 +68,20 @@ func NewKoboldMine() *KoboldMine {
Name: "Kobolds",
Value: 1000,
},
SectorMiningProductivity: cardsim.Stored[float64]{
Name: "Sector Mining Productivity",
SectorMiningIncome: cardsim.Stored[float64]{
Name: "Sector Mining Income",
Value: 0.15,
},
SectorScavengingProductivity: cardsim.Stored[float64]{
Name: "Sector Scavening Productivity",
SectorScavengingIncome: cardsim.Stored[float64]{
Name: "Sector Scavenging Income",
Value: 0.1,
},
GovBureaucracyProductivity: cardsim.Stored[float64]{
Name: "Government Bureaucracy Productivity",
GovBureaucracyExpense: cardsim.Stored[float64]{
Name: "Government Bureaucracy Expense",
Value: 0.05,
},
GovWarProductivity: cardsim.Stored[float64]{
Name: "Government War Productivity",
GovWarExpense: cardsim.Stored[float64]{
Name: "Government War Expense",
Value: 0.1,
},
}