From fb5aaeccfc46e95e57ab9ca69b7930ccc1439ae5 Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Mon, 3 Apr 2023 01:32:02 -0700 Subject: [PATCH] Implement first issue. This seems like the least complicated way to get the desired reusable behaviors. --- koboldsim/cards.go | 98 ++++++++++++++++++++++++++++++++++++ koboldsim/cardtypes.go | 110 ++++++++++++++++++++++++++++++++++------- 2 files changed, 191 insertions(+), 17 deletions(-) create mode 100644 koboldsim/cards.go diff --git a/koboldsim/cards.go b/koboldsim/cards.go new file mode 100644 index 0000000..0ad249a --- /dev/null +++ b/koboldsim/cards.go @@ -0,0 +1,98 @@ +package koboldsim + +import "git.chromaticdragon.app/kistaro/CardSimEngine/cardsim" + +var cards = []Card{ + &SwitchingCard{ + Name: cardsim.MsgStr("Warborn"), + Desc: cardsim.MsgStr(" A surge of anti-kobold sentiment has been reported by your spies on the surface and your military is concerned that anti-kobold vigilantes will attack the tunnels in the near-future. They want to build up now while doing so is safe."), + Policies: []Policy{ + &BasicPolicy{ + UnenactedDesc: cardsim.MsgStr(`Your war chief paces irritably. "We have terrible threats facing us. It's imperative that we build up our numbers in both the military and the domestic sense. I want creches under military control and a good hunting crew to supply them with food."`), + 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 + 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 + return nil + }, + }, + &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 + 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 + return nil + }, + }, + &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 + 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 + return nil + }, + }, + &VerbosePolicy{ + BasicPolicy: &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 + return cardsim.MsgStr("Creche control doesn't shift that easily."), nil + }, + Undo: func(p *Player) error { + p.Stats.Kobolds.Value -= 20 + return nil + }, + }, + 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."), + }, + { + Desc: cardsim.MsgStr(`"Dig deeper" pressures in your nation may be excessive.`), + Result: 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."), + }, + { + 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."), + }, + }, + }, + }, // end of "Warborn" policies + }, // end of "Warborn" card +} // end of card list + +func initDeck(d *cardsim.Deck[*KoboldMine]) { + for _, c := range cards { + d.Insert(cardsim.BottomOfDeck, c) + } + d.Shuffle() +} diff --git a/koboldsim/cardtypes.go b/koboldsim/cardtypes.go index 0ea40ec..9f4948f 100644 --- a/koboldsim/cardtypes.go +++ b/koboldsim/cardtypes.go @@ -16,14 +16,18 @@ type Policy interface { // Unenact reverses the previous enactment of this policy. Unenact(p *Player) error - // LastEnacted informs this Policy which choice was last enacted; it will - // need to prepare to describe itself differently depending on whether - // this is itself or not. The policy must return the CardOption that should - // be used further, which is usually self. + // 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(CardOption) CardOption + 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 @@ -35,7 +39,7 @@ type SwitchingCard struct { IsUrgent bool After func(*SwitchingCard, *Player, CardOption) error Policies []Policy - lastPolicy CardOption + lastPolicy Policy } // Title implements Card. @@ -59,24 +63,42 @@ func (s *SwitchingCard) EventText(*Player) (cardsim.Message, error) { } // Options implements Card. -func (s *SwitchingCard) Options(p *Player) ([]CardOption, error) { +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 { - ret[i] = p.LastEnacted(s.lastPolicy) + p.LastEnacted(lastIdx, s.lastPolicy) + ret[i] = p } return ret, nil } // Then implements Card. func (s *SwitchingCard) Then(p *Player, o CardOption) error { - s.lastPolicy = o - if s.After != nil { - return s.After(s, p, o) + newPolicy := o.(Policy) + var errs cardsim.ErrorCollector + if !newPolicy.Is(s.lastPolicy) { + err := s.lastPolicy.Unenact(p) + if cardsim.IsSeriousError(err) { + return err + } + errs.Add(err) } - return nil + 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. +// 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 @@ -94,18 +116,17 @@ func YesWeCan(*Player) bool { } // lastEnacted notifies b about the last-enacted policy in its group. It updates -// b.currentlyEnacted accordingly and returns itself as a generic CardOption. +// b.currentlyEnacted accordingly and returns itself. // // It also installs placeholders if CanDo or CanUndo is unspecified. -func (b *BasicPolicy) LastEnacted(c CardOption) CardOption { +func (b *BasicPolicy) LastEnacted(_ int, p Policy) { b.currentlyEnacted = false - if o, ok := c.(*BasicPolicy); ok && o == b { + if o, ok := p.(*BasicPolicy); ok && o == b { b.currentlyEnacted = true } if b.CanDo == nil { b.CanDo = YesWeCan } - return b } // OptionText implements CardOption. @@ -119,6 +140,10 @@ func (b *BasicPolicy) OptionText(*Player) (cardsim.Message, error) { // 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(p) { @@ -145,3 +170,54 @@ func (b *BasicPolicy) Enabled(p *Player) bool { } 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.LastEnacted(i, 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 +}