From ba5171fd670105fd7c926a908613e3bb1860a2e7 Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Sun, 29 Sep 2024 10:19:19 -0700 Subject: [PATCH] Prototype for TablePolicy --- .vscode/koboldsimsnippets.code-snippets | 27 ++++++++ koboldsim/cardtypes.go | 91 ++++++++++++++++++++++++- koboldsim/stats.go | 47 +++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 .vscode/koboldsimsnippets.code-snippets diff --git a/.vscode/koboldsimsnippets.code-snippets b/.vscode/koboldsimsnippets.code-snippets new file mode 100644 index 0000000..1265a88 --- /dev/null +++ b/.vscode/koboldsimsnippets.code-snippets @@ -0,0 +1,27 @@ +{ + // Place your KoboldSim workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } + "KoboldMine Add case": { + "scope": "go", + "prefix": "case", + "body": [ + "case ${1:field}:", + "\tk.${1:field} += amount", + "\treturn k.${1:field}", + ], + } +} \ No newline at end of file diff --git a/koboldsim/cardtypes.go b/koboldsim/cardtypes.go index 9a7b662..9336949 100644 --- a/koboldsim/cardtypes.go +++ b/koboldsim/cardtypes.go @@ -9,6 +9,7 @@ import ( var ( ErrOptionNotEnabled = errors.New("option not enabled") ErrPolicyNotEnacted = errors.New("cannot unenact policy that is not enacted") + ErrNoFieldLabel = errors.New("field does not exist") // ErrUnimplemented and ErrKeepMessaage are "non-errors". They are used // as special signals that the result needs to be handled in a special way; @@ -190,7 +191,7 @@ type BasicPolicy struct { } // YesWeCan returns true. It's the default value for BasicPolicy.CanDo / BasicPolicy.CanUndo. -func YesWeCan(*BasicPolicy, *Player) bool { +func YesWeCan[T any](T, *Player) bool { return true } @@ -260,6 +261,94 @@ func (b *BasicPolicy) Is(p Policy) bool { return false } +// TablePolicy is a Policy where all numerical changes are defined by +// adding a constant to some set of fields (defined by `EffectsTable``) +// and subtracting it back out when de-enacting. If the currently +// enacted option is re-enacted, it refunds the player's action point. +type TablePolicy struct { + Desc cardsim.Message + UnenactedDesc cardsim.Message + EnactedDesc cardsim.Message + NothingChanged cardsim.message + EffectsTable map[FieldLabel]float64 + CanDo func(*TablePolicy, *Player) bool + + CurrentlyEnacted bool + LastEnactedPolicy Policy + LastEnactedIdx int +} + +// LastEnacted notifies t about the last-enacted policy in its group. It updates +// t.currentlyEnacted accordingly. +func (t *TablePolicy) LastEnacted(i int, p Policy) { + t.LastEnactedPolicy = p + t.LastEnactedIdx = i + t.CurrentlyEnacted = t.Is(p) +} + +// OptionText implements CardOption. +func (t *TablePolicy) OptionText(*Player) (cardsim.Message, error) { + if t.CurrentlyEnacted { + if t.EnactedDesc == nil { + return nil, ErrUnimplemented + } + return t.EnactedDesc, nil + } + if t.UnenactedDesc == nil { + return nil, ErrUnimplemented + } + return t.UnenactedDesc, nil +} + +// Enact implements CardOption. +func (t *TablePolicy) Enact(p *Player) (cardsim.Message, error) { + if t.EffectsTable == nil { + return nil, ErrUnimplemented + } + if t.CurrentlyEnacted { + p.ActionsRemaining++ + if t.NothingChanged == nil { + t.NothingChanged = cardsim.MsgStr("You continue your current approach.") + } + return t.NothingChanged, nil + } + var errs cardsim.ErrorCollector + for label, amount := range t.EffectsTable { + errs.Add(p.Stats.Add(label, amount)) + } + return t.EnactedDesc, errs.Emit() +} + +// Unenact implements Policy. +func (t *TablePolicy) Unenact(p *Player) error { + if !t.CurrentlyEnacted { + return ErrPolicyNotEnacted + } + var errs cardsim.ErrorCollector + for label, amount := range t.EffectsTable { + errs.Add(p.Stats.Add(label, -amount) + } + return errs.Emit() +} + +// Enabled implements CardOption. +func (t *TablePolicy) Enabled(p *Player) bool { + if t.CurrentlyEnacted { + return true + } + if t.CanDo == nil { + panic(ErrUnimplemented) + } + return t.CanDo(t, p) +} + +func (t *TablePolicy) Is(p Policy) bool { + if o, ok := p.(*TablePolicy); ok { + return o == b + } + return false +} + // 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): diff --git a/koboldsim/stats.go b/koboldsim/stats.go index 87d6d33..9a11fca 100644 --- a/koboldsim/stats.go +++ b/koboldsim/stats.go @@ -164,3 +164,50 @@ func NewKoboldMine() *KoboldMine { Secrecy: 95, } } + +type FieldLabel string + +const ( + BasePopulation FieldLabel = "BasePopulation" + Scavenging FieldLabel = "Scavenging" + Militarism FieldLabel = "Militarism" + FoodSupply FieldLabel = "FoodSupply" + ForeignRelations FieldLabel = "ForeignRelations" + Rebellion FieldLabel = "Rebellion" + Madness FieldLabel = "Madness" + Cruelty FieldLabel = "Cruelty" + Authoritarianism FieldLabel = "Authoritarianism" +) + +func (k *KoboldMine) Add(which FieldLabel, amount float64) error { + switch(which) { + case BasePopulation: + k.BasePopulation += amount + return nil + case Scavenging: + k.Scavenging += amount + return nil + case Militarism: + k.Militarism += amount + return nil + case FoodSupply: + k.FoodSupply += amount + return nil + case ForeignRelations: + k.ForeignRelations += amount + return nil + case Rebellion: + k.Rebellion += amount + return nil + case Madness: + k.Madness += amount + return nil + case Cruelty: + k.Cruelty += amount + return nil + case Authoritarianism: + k.Authoritarianism += amount + return nil + } + return fmt.Errorf("cannot add %d to %q: %w", amount, which, ErrNoFieldLabel) +}