Compare commits
3 Commits
0e1f5ff246
...
175a2323f4
Author | SHA1 | Date | |
---|---|---|---|
175a2323f4 | |||
5728dd95f0 | |||
fb5aaeccfc |
99
koboldsim/cards.go
Normal file
99
koboldsim/cards.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
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."),
|
||||||
|
After: ShuffleIntoBottomHalf,
|
||||||
|
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()
|
||||||
|
}
|
@ -16,14 +16,18 @@ type Policy interface {
|
|||||||
// Unenact reverses the previous enactment of this policy.
|
// Unenact reverses the previous enactment of this policy.
|
||||||
Unenact(p *Player) error
|
Unenact(p *Player) error
|
||||||
|
|
||||||
// LastEnacted informs this Policy which choice was last enacted; it will
|
// LastEnacted informs this Policy which choice was last enacted and
|
||||||
// need to prepare to describe itself differently depending on whether
|
// which index it is under; this allows it to prepare to describe
|
||||||
// this is itself or not. The policy must return the CardOption that should
|
// itself differently depending on the last selected policy. If no
|
||||||
// be used further, which is usually self.
|
// 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,
|
// 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,
|
// 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
|
// A SwitchingCard is an issue card that remembers which option was selected
|
||||||
@ -33,9 +37,9 @@ type SwitchingCard struct {
|
|||||||
Name cardsim.Message
|
Name cardsim.Message
|
||||||
Desc cardsim.Message
|
Desc cardsim.Message
|
||||||
IsUrgent bool
|
IsUrgent bool
|
||||||
After func(*SwitchingCard, *Player, CardOption) error
|
After func(Card, *Player, CardOption) error
|
||||||
Policies []Policy
|
Policies []Policy
|
||||||
lastPolicy CardOption
|
lastPolicy Policy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title implements Card.
|
// Title implements Card.
|
||||||
@ -59,24 +63,42 @@ func (s *SwitchingCard) EventText(*Player) (cardsim.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Options implements Card.
|
// 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))
|
ret := make([]CardOption, len(s.Policies))
|
||||||
for i, p := range 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
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then implements Card.
|
// Then implements Card.
|
||||||
func (s *SwitchingCard) Then(p *Player, o CardOption) error {
|
func (s *SwitchingCard) Then(p *Player, o CardOption) error {
|
||||||
s.lastPolicy = o
|
newPolicy := o.(Policy)
|
||||||
if s.After != nil {
|
var errs cardsim.ErrorCollector
|
||||||
return s.After(s, p, o)
|
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 {
|
type BasicPolicy struct {
|
||||||
UnenactedDesc cardsim.Message
|
UnenactedDesc cardsim.Message
|
||||||
EnactedDesc 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
|
// 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.
|
// 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
|
b.currentlyEnacted = false
|
||||||
if o, ok := c.(*BasicPolicy); ok && o == b {
|
if o, ok := p.(*BasicPolicy); ok && o == b {
|
||||||
b.currentlyEnacted = true
|
b.currentlyEnacted = true
|
||||||
}
|
}
|
||||||
if b.CanDo == nil {
|
if b.CanDo == nil {
|
||||||
b.CanDo = YesWeCan
|
b.CanDo = YesWeCan
|
||||||
}
|
}
|
||||||
return b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionText implements CardOption.
|
// OptionText implements CardOption.
|
||||||
@ -119,6 +140,10 @@ func (b *BasicPolicy) OptionText(*Player) (cardsim.Message, error) {
|
|||||||
// 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.currentlyEnacted {
|
if b.currentlyEnacted {
|
||||||
|
p.ActionsRemaining++
|
||||||
|
if b.NothingChanged == nil {
|
||||||
|
b.NothingChanged = cardsim.MsgStr("You continue your current approach.")
|
||||||
|
}
|
||||||
return b.NothingChanged, nil
|
return b.NothingChanged, nil
|
||||||
}
|
}
|
||||||
if !b.CanDo(p) {
|
if !b.CanDo(p) {
|
||||||
@ -145,3 +170,60 @@ func (b *BasicPolicy) Enabled(p *Player) bool {
|
|||||||
}
|
}
|
||||||
return b.CanDo(p)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
@ -28,10 +28,6 @@ func InitPlayer() *Player {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDeck(*cardsim.Deck[*KoboldMine]) {
|
|
||||||
// TODO: move to cards.go, add cards
|
|
||||||
}
|
|
||||||
|
|
||||||
func initRules(*cardsim.RuleCollection[*KoboldMine]) {
|
func initRules(*cardsim.RuleCollection[*KoboldMine]) {
|
||||||
// TODO: move to rules.go, add rules
|
// TODO: move to rules.go, add rules
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user