Compare commits
15 Commits
main
...
Rewrite-St
Author | SHA1 | Date | |
---|---|---|---|
37d3b639bf | |||
|
39e4c94b6f | ||
|
99c5e5af6d | ||
a237fa81bf | |||
68cab7d2be | |||
2d16f97314 | |||
8a28f38d4d | |||
b2099586fc | |||
|
c4427885ca | ||
219ff33d66 | |||
5b860135e9 | |||
d2a00d2044 | |||
ba5171fd67 | |||
|
e018bd0ad6 | ||
|
16f452b08f |
18
.vscode/koboldsimsnippets.code-snippets
vendored
Normal file
18
.vscode/koboldsimsnippets.code-snippets
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
// 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"
|
||||||
|
// }
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -8,5 +8,5 @@ require (
|
|||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||||
)
|
)
|
||||||
|
1239
koboldsim/cards.go
1239
koboldsim/cards.go
File diff suppressed because it is too large
Load Diff
@ -260,6 +260,99 @@ func (b *BasicPolicy) Is(p Policy) bool {
|
|||||||
return false
|
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
|
||||||
|
EnactionDesc cardsim.Message
|
||||||
|
CanDo func(*TablePolicy, *Player) bool
|
||||||
|
|
||||||
|
CurrentlyEnacted bool
|
||||||
|
LastEnactedPolicy Policy
|
||||||
|
LastEnactedIdx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func YesWeAlsoCan(*TablePolicy, *Player) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.EnactionDesc, 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 == t
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// A VerbosePolicy is a group of related policies pretending to all be the same
|
// 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
|
// policy. Which policy is used is determined by what the previous policy for
|
||||||
// the card was (as reported via a call to LastEnacted):
|
// the card was (as reported via a call to LastEnacted):
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package koboldsim
|
package koboldsim
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"git.chromaticdragon.app/kistaro/CardSimEngine/cardsim"
|
"git.chromaticdragon.app/kistaro/CardSimEngine/cardsim"
|
||||||
)
|
)
|
||||||
@ -9,280 +11,86 @@ import (
|
|||||||
// KoboldMine is the state of a kobold mine.
|
// KoboldMine is the state of a kobold mine.
|
||||||
type KoboldMine struct {
|
type KoboldMine struct {
|
||||||
BasePopulation float64 `cardsim:"stathidden"`
|
BasePopulation float64 `cardsim:"stathidden"`
|
||||||
|
DragonCount float64 `cardsim:"stat" cardsim_name:"Dragon Population"`
|
||||||
|
|
||||||
MiningIncome float64 `cardsim:"stathidden" cardsim_name:"Mining Productivity"`
|
Mining float64 `cardsim:"stathidden" cardsim_name:"Mining"`
|
||||||
ScavengingIncome float64 `cardsim:"stathidden" cardsim_name:"Scavenging Productivity"`
|
Scavenging float64 `cardsim:"stathidden" cardsim_name:"Scavenging"`
|
||||||
AlchemyIncome float64 `cardsim:"stathidden" cardsim_name:"Alchemy Productivity"`
|
Alchemy float64 `cardsim:"stathidden" cardsim_name:"Alchemy"`
|
||||||
HospitalityIncome float64 `cardsim:"stathidden" cardsim_name:"Hospitality Productivity"`
|
Hospitality float64 `cardsim:"stathidden" cardsim_name:"Hospitality"`
|
||||||
AgricultureIncome float64 `cardsim:"stathidden" cardsim_name:"Agriculture Productivity"`
|
Agriculture float64 `cardsim:"stathidden" cardsim_name:"Agriculture"`
|
||||||
ManufacturingIncome float64 `cardsim:"stathidden" cardsim_name:"Manufacturing Productivity"`
|
Manufacturing float64 `cardsim:"stathidden" cardsim_name:"Manufacturing"`
|
||||||
PlanarIncome float64 `cardsim:"stathidden" cardsim_name:"Planar Productivity"`
|
PlanarConnections float64 `cardsim:"stathidden" cardsim_name:"Planar Connections"`
|
||||||
PublishingIncome float64 `cardsim:"stathidden" cardsim_name:"Publishing Productivity"`
|
Publishing float64 `cardsim:"stathidden" cardsim_name:"Publishing"`
|
||||||
ForestryIncome float64 `cardsim:"stathidden" cardsim_name:"Forestry Productivity"`
|
Forestry float64 `cardsim:"stathidden" cardsim_name:"Forestry"`
|
||||||
FinanceIncome float64 `cardsim:"stathidden" cardsim_name:"Finance Productivity"`
|
Finance float64 `cardsim:"stathidden" cardsim_name:"Finance"`
|
||||||
GadgetryIncome float64 `cardsim:"stathidden" cardsim_name:"Gadgetry Productivity"`
|
Gadgetry float64 `cardsim:"stathidden" cardsim_name:"Gadgetry"`
|
||||||
FishingIncome float64 `cardsim:"stathidden" cardsim_name:"Fishing Productivity"`
|
Fishing float64 `cardsim:"stathidden" cardsim_name:"Fishing"`
|
||||||
ConstructionIncome float64 `cardsim:"stathidden" cardsim_name:"Construction Productivity"`
|
Construction float64 `cardsim:"stathidden" cardsim_name:"Construction"`
|
||||||
|
|
||||||
PropagandaExpense float64 `cardsim:"stathidden" cardsim_name:"Propaganda Investment"`
|
Propaganda float64 `cardsim:"stathidden" cardsim_name:"Propaganda"`
|
||||||
BureaucracyExpense float64 `cardsim:"stathidden" cardsim_name:"Bureaucracy Investment"`
|
Bureaucracy float64 `cardsim:"stathidden" cardsim_name:"Bureaucracy"`
|
||||||
WarExpense float64 `cardsim:"stathidden" cardsim_name:"War Investment"`
|
Militarism float64 `cardsim:"stathidden" cardsim_name:"Militarism"`
|
||||||
QoLExpense float64 `cardsim:"stathidden" cardsim_name:"QoL Investment"`
|
Welfare float64 `cardsim:"stathidden" cardsim_name:"Social Safety Net"`
|
||||||
LogisticsExpense float64 `cardsim:"stathidden" cardsim_name:"Logistics Investment"`
|
Logistics float64 `cardsim:"stathidden" cardsim_name:"Logistics"`
|
||||||
DragonSubsExpense float64 `cardsim:"stathidden" cardsim_name:"Dragon Subsidies Investment"`
|
DragonSubs float64 `cardsim:"stathidden" cardsim_name:"Dragon Subsidies"`
|
||||||
ResearchSubsExpense float64 `cardsim:"stathidden" cardsim_name:"Research Subsidies Investment"`
|
ResearchSubs float64 `cardsim:"stathidden" cardsim_name:"Research"`
|
||||||
EducationExpense float64 `cardsim:"stathidden" cardsim_name:"Education Investment"`
|
Education float64 `cardsim:"stathidden" cardsim_name:"Education"`
|
||||||
HealthcareExpense float64 `cardsim:"stathidden" cardsim_name:"Healthcare Investment"`
|
Healthcare float64 `cardsim:"stathidden" cardsim_name:"Healthcare"`
|
||||||
ForeignRelExpense float64 `cardsim:"stathidden" cardsim_name:"Foreign Relations Investment"`
|
ForeignRelExpense float64 `cardsim:"stathidden" cardsim_name:"Diplomatic Investment"`
|
||||||
PoliceExpense float64 `cardsim:"stathidden" cardsim_name:"Law Enforcement Investment"`
|
ParksExpense float64 `cardsim:"stathidden" cardsim_name:"City Beautification"`
|
||||||
EconPlanExpense float64 `cardsim:"stathidden" cardsim_name:"Economic Planning Investment"`
|
Faith float64 `cardsim:"stathidden" cardsim_name:"Faith"`
|
||||||
ParksExpense float64 `cardsim:"stathidden" cardsim_name:"Parks and Aesthetics Investment"`
|
|
||||||
FaithExpense float64 `cardsim:"stathidden" cardsim_name:"Faith Investment"`
|
|
||||||
|
|
||||||
FoodSupply float64 `cardsim:"stathidden"`
|
FoodSupply float64 `cardsim:"stathidden"`
|
||||||
ObesogenicFood float64 `cardsim:"stathidden"`
|
|
||||||
ForeignRelations float64 `cardsim:"stathidden"`
|
ForeignRelations float64 `cardsim:"stathidden"`
|
||||||
HiddenRelPenalty float64 `cardsim:"stathidden"` // Lower is better.
|
HiddenRelPenalty float64 `cardsim:"stathidden"` // Lower is better.
|
||||||
Secrecy float64 `cardsim:"stathidden"`
|
Secrecy float64 `cardsim:"stathidden"`
|
||||||
PointOfDimReturns float64 `cardsim:"stathidden"`
|
|
||||||
Rebellion float64 `cardsim:"stathidden"`
|
Rebellion float64 `cardsim:"stathidden"`
|
||||||
Madness float64 `cardsim:"stathidden"`
|
Madness float64 `cardsim:"stathidden"`
|
||||||
Cruelty float64 `cardsim:"stat"`
|
Cruelty float64 `cardsim:"stat"`
|
||||||
Greed float64 `cardsim:"stat"`
|
Greed float64 `cardsim:"stathidden"`
|
||||||
Gullibility float64 `cardsim:"stathidden"`
|
Gullibility float64 `cardsim:"stathidden"`
|
||||||
Authoritarianism float64 `cardsim:"stat"`
|
Authoritarianism float64 `cardsim:"stat"`
|
||||||
|
|
||||||
// AnotherExpense float64 `cardsim:"hiddenround5"`
|
Taxation float64 `cardsim:"stat"`
|
||||||
// A different way of adding stats that is slightly more empowering.
|
TaxEvasion float64 `cardsim:"stat"`
|
||||||
}
|
Squalor float64 `cardsim:"stat"`
|
||||||
|
// Idea for tax evasion. The corruption stat should increase tax evasion, while the squalor stat should reduce it. The link with corruption should be obvious, but for squalor: when the economy improves, more people have resources with which to attempt to protect their income. This is part of why rich people can evade taxes more effectively than poor people. It's not ALL lobbying advantage.
|
||||||
|
|
||||||
func (k *KoboldMine) ProductivityFunc(s float64) func() float64 {
|
|
||||||
return func() float64 {
|
|
||||||
return math.Max(s*float64(k.Kobolds()), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) ProductivityTotal() float64 {
|
|
||||||
total := math.Max(k.MiningIncome, 0.01)
|
|
||||||
total += math.Max(k.ScavengingIncome, 0)
|
|
||||||
total += math.Max(k.AlchemyIncome, 0)
|
|
||||||
total += math.Max(k.HospitalityIncome, 0)
|
|
||||||
total += math.Max(k.AgricultureIncome, 0)
|
|
||||||
total += math.Max(k.ManufacturingIncome, 0)
|
|
||||||
total += math.Max(k.PlanarIncome, 0)
|
|
||||||
total += math.Max(k.PublishingIncome, 0)
|
|
||||||
total += math.Max(k.FinanceIncome, 0)
|
|
||||||
total += math.Max(k.GadgetryIncome, 0)
|
|
||||||
total += math.Max(k.FishingIncome, 0)
|
|
||||||
total += math.Max(k.ConstructionIncome, 0.02)
|
|
||||||
total += math.Max(k.PropagandaExpense, 0)
|
|
||||||
total += math.Max(k.BureaucracyExpense, 0)
|
|
||||||
total += math.Max(k.WarExpense, 0)
|
|
||||||
total += math.Max(k.QoLExpense, 0)
|
|
||||||
total += math.Max(k.LogisticsExpense, 0)
|
|
||||||
total += math.Max(k.DragonSubsExpense, 0)
|
|
||||||
total += math.Max(k.ResearchSubsExpense, 0)
|
|
||||||
total += math.Max(k.EducationExpense, 0)
|
|
||||||
total += math.Max(k.HealthcareExpense, 0)
|
|
||||||
total += math.Max(k.ForeignRelExpense, 0)
|
|
||||||
total += math.Max(k.PoliceExpense, 0)
|
|
||||||
total += math.Max(k.EconPlanExpense, 0)
|
|
||||||
total += math.Max(k.ParksExpense, 0)
|
|
||||||
total += math.Max(k.FaithExpense, 0)
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) ProductivityMultiplier() float64 {
|
|
||||||
return math.Pow(0.95, math.Max(0, k.ProductivityTotal()-k.PointOfDimReturns))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueMiningIncome() float64 {
|
|
||||||
return math.Max(k.MiningIncome*k.ProductivityMultiplier(), 0.01)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueScavengingIncome() float64 {
|
|
||||||
return math.Max(k.ScavengingIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueAlchemyIncome() float64 {
|
|
||||||
return math.Max(k.AlchemyIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueHospitalityIncome() float64 {
|
|
||||||
return math.Max(k.HospitalityIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueAgricultureIncome() float64 {
|
|
||||||
return math.Max(k.AgricultureIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueManufacturingIncome() float64 {
|
|
||||||
return math.Max(k.ManufacturingIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TruePlanarIncome() float64 {
|
|
||||||
return math.Max(k.PlanarIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TruePublishingIncome() float64 {
|
|
||||||
return math.Max(k.PublishingIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueForestryIncome() float64 {
|
|
||||||
return math.Max(k.ForestryIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueFinanceIncome() float64 {
|
|
||||||
return math.Max(k.FinanceIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueGadgetryIncome() float64 {
|
|
||||||
return math.Max(k.GadgetryIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueFishingIncome() float64 {
|
|
||||||
return math.Max(k.FishingIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueConstructionIncome() float64 {
|
|
||||||
return math.Max(k.ConstructionIncome*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TruePropagandaExpense() float64 {
|
|
||||||
return math.Max(k.PropagandaExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueBureaucracyExpense() float64 {
|
|
||||||
return math.Max(k.BureaucracyExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueWarExpense() float64 {
|
|
||||||
return math.Max(k.WarExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueQoLExpense() float64 {
|
|
||||||
return math.Max(k.QoLExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueLogisticsExpense() float64 {
|
|
||||||
return math.Max(k.LogisticsExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueDragonSubsExpense() float64 {
|
|
||||||
return math.Max(k.DragonSubsExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueResearchSubsExpense() float64 {
|
|
||||||
return math.Max(k.ResearchSubsExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueEducationExpense() float64 {
|
|
||||||
return math.Max(k.EducationExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueHealthcareExpense() float64 {
|
|
||||||
return math.Max(k.HealthcareExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueForeignRelExpense() float64 {
|
|
||||||
return math.Max(k.ForeignRelExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TruePoliceExpense() float64 {
|
|
||||||
return math.Max(k.PoliceExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueEconPlanExpense() float64 {
|
|
||||||
return math.Max(k.EconPlanExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueParksExpense() float64 {
|
|
||||||
return math.Max(k.ParksExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueFaithExpense() float64 {
|
|
||||||
return math.Max(k.FaithExpense*k.ProductivityMultiplier(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TotalSectorIncome() float64 {
|
|
||||||
return float64(k.Kobolds()) * (math.Max(k.TrueMiningIncome(), 0.01) + math.Max(k.TrueScavengingIncome(), 0) + math.Max(k.TrueAlchemyIncome(), 0) + math.Max(k.TrueHospitalityIncome(), 0) + math.Max(k.TrueAgricultureIncome(), 0) + math.Max(k.TrueManufacturingIncome(), 0) + math.Max(k.TruePlanarIncome(), 0) + math.Max(k.TruePublishingIncome(), 0) + math.Max(k.TrueFinanceIncome(), 0) + math.Max(k.TrueGadgetryIncome(), 0) + math.Max(k.TrueFishingIncome(), 0) + math.Max(k.TrueConstructionIncome(), 0.02))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TotalGovExpense() float64 {
|
|
||||||
return float64(k.Kobolds()) * (math.Max(k.TruePropagandaExpense(), 0) + math.Max(k.TrueBureaucracyExpense(), 0) + math.Max(k.TrueWarExpense(), 0) + math.Max(k.TrueQoLExpense(), 0) + math.Max(k.TrueLogisticsExpense(), 0) + math.Max(k.TrueDragonSubsExpense(), 0) + math.Max(k.TrueResearchSubsExpense(), 0) + math.Max(k.TrueEducationExpense(), 0) + math.Max(k.TrueHealthcareExpense(), 0) + math.Max(k.TrueForeignRelExpense(), 0) + math.Max(k.TruePoliceExpense(), 0) + math.Max(k.TrueEconPlanExpense(), 0) + math.Max(k.TrueParksExpense(), 0) + math.Max(k.TrueFaithExpense(), 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) Taxation() float64 {
|
|
||||||
return (k.TotalGovExpense() / (k.TotalSectorIncome() + k.TotalGovExpense())) * 100
|
|
||||||
}
|
|
||||||
|
|
||||||
// Idea for the tax rate. I could have a tracked "tax evasion" stat that drives up taxation (people who actually pay have to pay more) as well as a tracked "public pay ratio" stat that drives up taxation if the public sector is paid more than the private sector and vice versa if the public sector is paid less. Both stats should be exposed to the player.
|
|
||||||
// Idea for tax evasion. The corruption stat should increase tax evasion, while the squalor stat should reduce it. The link with corruption should be obvious, but for squalor: when the economy improves, more people have resources with which to attempt to protect their income.
|
|
||||||
|
|
||||||
func (k *KoboldMine) StatTaxRate() float64 {
|
|
||||||
return k.Taxation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KoboldMine) Kobolds() int64 {
|
func (k *KoboldMine) Kobolds() int64 {
|
||||||
return int64((k.BasePopulation + (k.HealthcareExpense * 1000)) * k.FoodSupply * (1 - 0.5*(k.StatObesity()/100)) * (1 + k.TrueForeignRelations()))
|
return int64((k.BasePopulation * (k.Healthcare + 1)) * (k.FoodSupply / 10) * (1 + k.TrueForeignRelations()/100))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MinFoodSupply = 18
|
||||||
|
MaxFoodSupply = 37
|
||||||
|
MinForeignRelations = -11
|
||||||
|
MaxForeignRelations = 9
|
||||||
|
)
|
||||||
|
|
||||||
func (k *KoboldMine) DisplayedFoodSupply() float64 {
|
func (k *KoboldMine) DisplayedFoodSupply() float64 {
|
||||||
return (k.FoodSupply - 1) * 100
|
return 100 * (k.FoodSupply - MinFoodSupply) / (MaxFoodSupply - MinFoodSupply) //This returns a Policy Implementation Percentage, so that the player can see how good they are at capturing food supply. When it becomes possible for Food Supply to hit 0 and end the game in famine, the minimum survivable food supply PIP should be calculated and returned to the player as a warned-against failure threshold.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KoboldMine) StatObesogenicity() float64 {
|
//func (k *KoboldMine) StatObesity() float64 {
|
||||||
return (k.ObesogenicFood - 1) * 100
|
//return (100 / (2.3 + math.Exp(-0.04*(k.DisplayedFoodSupply()-37)))) + 100/(2.3+math.Exp(-0.04*(k.StatObesogenicity()-37)))
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (k *KoboldMine) StatObesity() float64 {
|
|
||||||
return (100 / (2.3 + math.Exp(-0.04*(k.DisplayedFoodSupply()-37)))) + 100/(2.3+math.Exp(-0.04*(k.StatObesogenicity()-37)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) TrueForeignRelations() float64 {
|
func (k *KoboldMine) TrueForeignRelations() float64 {
|
||||||
return (1 - math.Max(math.Min(k.Secrecy, 1), 0)) * (math.Max(math.Min(k.ForeignRelations, 1), -1) - k.HiddenRelPenalty)
|
openness := 100 - clamp(k.Secrecy, 0, 100)
|
||||||
|
effectiveRel := clamp(k.ForeignRelations, -100, 100) - k.HiddenRelPenalty
|
||||||
|
return openness / 100 * effectiveRel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KoboldMine) DisplayedForeignRelations() float64 {
|
func (k *KoboldMine) DisplayedForeignRelations() float64 {
|
||||||
return math.Max(math.Min((k.ForeignRelations*100), 100), -100)
|
return 100 * (k.ForeignRelations - MinForeignRelations) / (MaxForeignRelations - MinForeignRelations)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KoboldMine) DisplayedSecrecy() float64 {
|
func (k *KoboldMine) DisplayedSecrecy() float64 {
|
||||||
return k.Secrecy * 100
|
return k.Secrecy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KoboldMine) SqualorReduction() float64 {
|
|
||||||
total := math.Max(k.MiningIncome, 0.01)
|
|
||||||
total -= math.Max(k.ScavengingIncome, 0)
|
|
||||||
total += math.Max(k.AlchemyIncome, 0)
|
|
||||||
total += math.Max(k.HospitalityIncome, 0) * 2
|
|
||||||
total += math.Max(k.ManufacturingIncome, 0)
|
|
||||||
total += math.Max(k.PlanarIncome, 0)
|
|
||||||
total += math.Max(k.PublishingIncome, 0) * 2
|
|
||||||
total += math.Max(k.FinanceIncome, 0) * 3
|
|
||||||
total += math.Max(k.GadgetryIncome, 0) * 2
|
|
||||||
total += math.Max(k.ConstructionIncome, 0.02) * 2
|
|
||||||
total += math.Max(k.PropagandaExpense, 0)
|
|
||||||
total += math.Max(k.BureaucracyExpense, 0) * 2
|
|
||||||
total += math.Max(k.QoLExpense, 0) * 20
|
|
||||||
total += math.Max(k.LogisticsExpense, 0)
|
|
||||||
total += math.Max(k.ResearchSubsExpense, 0) * 2
|
|
||||||
total += math.Max(k.EducationExpense, 0) * 4
|
|
||||||
total += math.Max(k.HealthcareExpense, 0) * 6
|
|
||||||
total += math.Max(k.ForeignRelExpense, 0)
|
|
||||||
total += math.Max(k.PoliceExpense, 0)
|
|
||||||
total += math.Max(k.EconPlanExpense, 0)
|
|
||||||
total += math.Max(k.ParksExpense, 0) * 3
|
|
||||||
total += math.Max(k.FaithExpense, 0) * 10
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KoboldMine) StatSqualor() float64 {
|
|
||||||
return 100 * math.Pow(1.204, 1.2-(2*k.SqualorReduction()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// So I want squalor to be a value between 100 and 0 that reduces readily at the start and then becomes increasingly difficult to further reduce. This initial squalor stat starts at about 80%, but may not change rapidly enough. Note that the 1.2-(2* sequence is meant to balance it at 80% starting point. If I change the velocity on the 2* side, I have to change the 1.2 offset as well. I really hope this math works, because I don't quite understand it.
|
|
||||||
|
|
||||||
func (k *KoboldMine) StatChaos() float64 {
|
func (k *KoboldMine) StatChaos() float64 {
|
||||||
return Mean(k.Rebellion, k.Madness, k.Cruelty)
|
return Mean(k.Rebellion, k.Madness, k.Cruelty)
|
||||||
}
|
}
|
||||||
@ -293,127 +101,11 @@ func (k *KoboldMine) StatCorruption() float64 {
|
|||||||
return Mean(k.Greed, k.Gullibility, k.Authoritarianism)
|
return Mean(k.Greed, k.Gullibility, k.Authoritarianism)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the "crimes of cunning" stat for my crime calculations. It will also be displayed to the player as just Corruption. The player can also see Greed and Authoritarianism, so they should have a good idea what goes into Corruption, but they'll have to infer the importance of Gullibility.
|
// This is the "crimes of cunning" stat for my crime calculations. It will also be displayed to the player as just Corruption. The player can see Authoritarianism, but they'll have to infer the importance of Greed and Gullibility.
|
||||||
|
|
||||||
func (k *KoboldMine) Stats() []cardsim.Stat {
|
func (k *KoboldMine) Stats() []cardsim.Stat {
|
||||||
stats := cardsim.ExtractStats(k)
|
stats := cardsim.ExtractStats(k)
|
||||||
funcs := []cardsim.Stat{
|
funcs := []cardsim.Stat{
|
||||||
cardsim.StatFunc(
|
|
||||||
"Mining Income",
|
|
||||||
k.ProductivityFunc(k.TrueMiningIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Scavenging Income",
|
|
||||||
k.ProductivityFunc(k.TrueScavengingIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Alchemy Income",
|
|
||||||
k.ProductivityFunc(k.TrueAlchemyIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Hospitality Income",
|
|
||||||
k.ProductivityFunc(k.TrueHospitalityIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Agriculture Income",
|
|
||||||
k.ProductivityFunc(k.TrueAgricultureIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Manufacturing Income",
|
|
||||||
k.ProductivityFunc(k.TrueManufacturingIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Planar Harvesting Income",
|
|
||||||
k.ProductivityFunc(k.TruePlanarIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Book Publishing Income",
|
|
||||||
k.ProductivityFunc(k.TruePublishingIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Forestry Income",
|
|
||||||
k.ProductivityFunc(k.TrueForestryIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Finance Income",
|
|
||||||
k.ProductivityFunc(k.TrueFinanceIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Gadgetry Income",
|
|
||||||
k.ProductivityFunc(k.TrueGadgetryIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Fishing Income",
|
|
||||||
k.ProductivityFunc(k.TrueFishingIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Construction Income",
|
|
||||||
k.ProductivityFunc(k.TrueConstructionIncome()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Propaganda Expense",
|
|
||||||
k.ProductivityFunc(k.TruePropagandaExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Bureaucracy Expense",
|
|
||||||
k.ProductivityFunc(k.TrueBureaucracyExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"War Expense",
|
|
||||||
k.ProductivityFunc(k.TrueWarExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"QoL Expense",
|
|
||||||
k.ProductivityFunc(k.TrueQoLExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Logistics Expense",
|
|
||||||
k.ProductivityFunc(k.TrueLogisticsExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Dragon Subsidies",
|
|
||||||
k.ProductivityFunc(k.TrueDragonSubsExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Research Subsidies",
|
|
||||||
k.ProductivityFunc(k.TrueResearchSubsExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Education Expense",
|
|
||||||
k.ProductivityFunc(k.TrueEducationExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Healthcare Expense",
|
|
||||||
k.ProductivityFunc(k.TrueHealthcareExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Foreign Relations Expense",
|
|
||||||
k.ProductivityFunc(k.TrueForeignRelExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Law Enforcement Expense",
|
|
||||||
k.ProductivityFunc(k.TruePoliceExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Economic Planning Expense",
|
|
||||||
k.ProductivityFunc(k.TrueEconPlanExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Parks and Aesthetics Expense",
|
|
||||||
k.ProductivityFunc(k.TrueParksExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Faith Expense",
|
|
||||||
k.ProductivityFunc(k.TrueFaithExpense()),
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Total Sector Income",
|
|
||||||
k.TotalSectorIncome,
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Total Government Expense",
|
|
||||||
k.TotalGovExpense,
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
cardsim.StatFunc(
|
||||||
"Foreign Relations",
|
"Foreign Relations",
|
||||||
k.DisplayedForeignRelations,
|
k.DisplayedForeignRelations,
|
||||||
@ -430,14 +122,6 @@ func (k *KoboldMine) Stats() []cardsim.Stat {
|
|||||||
"Kobolds",
|
"Kobolds",
|
||||||
k.Kobolds,
|
k.Kobolds,
|
||||||
),
|
),
|
||||||
cardsim.InvisibleStatFunc(
|
|
||||||
"Squalor Reduction",
|
|
||||||
k.SqualorReduction,
|
|
||||||
),
|
|
||||||
cardsim.StatFunc(
|
|
||||||
"Squalor",
|
|
||||||
k.StatSqualor,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
stats = append(stats, funcs...)
|
stats = append(stats, funcs...)
|
||||||
// cardsim.SortStats(stats)
|
// cardsim.SortStats(stats)
|
||||||
@ -447,45 +131,118 @@ func (k *KoboldMine) Stats() []cardsim.Stat {
|
|||||||
func NewKoboldMine() *KoboldMine {
|
func NewKoboldMine() *KoboldMine {
|
||||||
return &KoboldMine{
|
return &KoboldMine{
|
||||||
BasePopulation: 1025,
|
BasePopulation: 1025,
|
||||||
MiningIncome: 0.15,
|
Mining: 0,
|
||||||
ScavengingIncome: 0.1,
|
Scavenging: 0,
|
||||||
AlchemyIncome: 0.01,
|
Alchemy: 0,
|
||||||
HospitalityIncome: 0.0,
|
Hospitality: 0,
|
||||||
AgricultureIncome: 0.0,
|
Agriculture: 0,
|
||||||
ManufacturingIncome: 0.10,
|
Manufacturing: 0,
|
||||||
PlanarIncome: 0.00,
|
PlanarConnections: 0,
|
||||||
PublishingIncome: 0.02,
|
Publishing: 0,
|
||||||
ForestryIncome: 0.0,
|
Forestry: 0,
|
||||||
FinanceIncome: 0.02,
|
Finance: 0,
|
||||||
GadgetryIncome: 0.03,
|
Gadgetry: 0,
|
||||||
FishingIncome: 0.0,
|
Fishing: 0,
|
||||||
ConstructionIncome: 0.05,
|
Construction: 0,
|
||||||
PropagandaExpense: 0.01,
|
Propaganda: 0,
|
||||||
BureaucracyExpense: 0.05,
|
Bureaucracy: 0,
|
||||||
WarExpense: 0.1,
|
Militarism: 0,
|
||||||
QoLExpense: 0.01,
|
Welfare: 0,
|
||||||
LogisticsExpense: 0.02,
|
Logistics: 0,
|
||||||
DragonSubsExpense: 0.0,
|
DragonSubs: 0,
|
||||||
ResearchSubsExpense: 0.0,
|
ResearchSubs: 0,
|
||||||
EducationExpense: 0.01,
|
Education: 0,
|
||||||
HealthcareExpense: 0.01,
|
Healthcare: 0,
|
||||||
ForeignRelExpense: 0.0,
|
ForeignRelExpense: 0,
|
||||||
PoliceExpense: 0.03,
|
ParksExpense: 0,
|
||||||
EconPlanExpense: 0.02,
|
Faith: 0,
|
||||||
ParksExpense: 0.0,
|
FoodSupply: 20,
|
||||||
FaithExpense: 0.03,
|
ForeignRelations: 0,
|
||||||
FoodSupply: 0.20,
|
HiddenRelPenalty: 0,
|
||||||
ForeignRelations: -0.40,
|
Rebellion: 0,
|
||||||
HiddenRelPenalty: -0.01,
|
Madness: 0,
|
||||||
Rebellion: 0.10,
|
Cruelty: 0,
|
||||||
Madness: 0.25,
|
Greed: 0,
|
||||||
Cruelty: 0.45,
|
Gullibility: 0,
|
||||||
Greed: 0.35,
|
Authoritarianism: 0,
|
||||||
Gullibility: 0.10,
|
Secrecy: 95,
|
||||||
Authoritarianism: 0.70,
|
|
||||||
Secrecy: .95,
|
|
||||||
PointOfDimReturns: 1.0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that the mine initializes with 1.02 points of overall productivity.
|
// FieldLabel instances are strings exactly matching the name of an
|
||||||
|
// exported field in `KoboldMine`. These are used to map field names to
|
||||||
|
// amounts to change in `TablePolicy` instances, which are then looked
|
||||||
|
// up by name (via reflection) when adding the stat.
|
||||||
|
type FieldLabel string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Alchemy FieldLabel = "Alchemy"
|
||||||
|
Authoritarianism FieldLabel = "Authoritarianism"
|
||||||
|
BasePopulation FieldLabel = "BasePopulation"
|
||||||
|
Bureaucracy FieldLabel = "Bureaucracy"
|
||||||
|
Construction FieldLabel = "Construction"
|
||||||
|
Cruelty FieldLabel = "Cruelty"
|
||||||
|
Education FieldLabel = "Education"
|
||||||
|
FoodSupply FieldLabel = "FoodSupply"
|
||||||
|
ForeignRelations FieldLabel = "ForeignRelations"
|
||||||
|
ForeignRelExpense FieldLabel = "ForeignRelExpense"
|
||||||
|
Gadgetry FieldLabel = "Gadgetry"
|
||||||
|
Greed FieldLabel = "Greed"
|
||||||
|
Gullibility FieldLabel = "Gullibility"
|
||||||
|
Healthcare FieldLabel = "Healthcare"
|
||||||
|
HiddenRelPenalty FieldLabel = "HiddenRelPenalty"
|
||||||
|
Hospitality FieldLabel = "Hospitality"
|
||||||
|
Logistics FieldLabel = "Logistics"
|
||||||
|
Madness FieldLabel = "Madness"
|
||||||
|
Manufacturing FieldLabel = "Manufacturing"
|
||||||
|
Militarism FieldLabel = "Militarism"
|
||||||
|
Mining FieldLabel = "Mining"
|
||||||
|
ParksExpense FieldLabel = "ParksExpense"
|
||||||
|
Publishing FieldLabel = "Publishing"
|
||||||
|
Rebellion FieldLabel = "Rebellion"
|
||||||
|
Scavenging FieldLabel = "Scavenging"
|
||||||
|
Secrecy FieldLabel = "Secrecy"
|
||||||
|
Welfare FieldLabel = "Welfare"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrBadFieldLabel is an "error category" for all errors where a
|
||||||
|
// FieldLabel did not correctly name a Field that could be used in Add.
|
||||||
|
var ErrBadFieldLabel = errors.New("bad field label")
|
||||||
|
|
||||||
|
// ErrNoFieldLabel is a kind of `ErrBadFieldLabel` used when no field
|
||||||
|
// of the target has the exact name specified in the FieldLabel. Check
|
||||||
|
// spelling and capitalization.
|
||||||
|
var ErrNoFieldLabel = fmt.Errorf("%w: field does not exist", ErrBadFieldLabel)
|
||||||
|
|
||||||
|
// ErrFieldNotFloat is a kind of `ErrBadFieldLabel` used when it is not
|
||||||
|
// possible to read and assign a float to the named field. Is this the
|
||||||
|
// name of a calculated stat (a function) rather than a base stat?
|
||||||
|
var ErrFieldNotFloat = fmt.Errorf("%w: field type is not float", ErrBadFieldLabel)
|
||||||
|
|
||||||
|
// ErrFieldSetPanic is a kind of `ErrBadFieldLabel` used when trying to
|
||||||
|
// set the value of a field panicked. The panic message should be
|
||||||
|
// preserved in the error to diagnose the problem. If the problem is
|
||||||
|
// that the field is unexported, capitalize the name (including inside
|
||||||
|
// the KoboldMine type). Any other issue is a more complicated bug,
|
||||||
|
// because all of them that aren't already caught by ErrFieldNotFloat
|
||||||
|
// imply something very unexpected happened with `k` itself (in `Add`).
|
||||||
|
var ErrFieldSetPanic = fmt.Errorf("%w: panic when setting", ErrBadFieldLabel)
|
||||||
|
|
||||||
|
// Use a FieldLabel to add an amount to the matching field. If no such
|
||||||
|
// field can be found, the field is not exported, or the field is not
|
||||||
|
// of type float64, this returns an error ad does not change any values.
|
||||||
|
func (k *KoboldMine) Add(which FieldLabel, amount float64) error {
|
||||||
|
kv := reflect.ValueOf(k).Elem()
|
||||||
|
f := kv.FieldByName(string(which))
|
||||||
|
if !f.IsValid() {
|
||||||
|
return fmt.Errorf("cannot add %f to field %q: %w", amount, which, ErrNoFieldLabel)
|
||||||
|
}
|
||||||
|
if !f.CanFloat() {
|
||||||
|
return fmt.Errorf("cannot add %f to field %q: %w", amount, which, ErrFieldNotFloat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := try(func() { f.SetFloat(f.Float() + amount) }); err != nil {
|
||||||
|
return fmt.Errorf("could not add %f to field %q: %w", amount, which, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
package koboldsim
|
package koboldsim
|
||||||
|
|
||||||
import "math"
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
// Generic helper functions not directly attached to Card Sim Engine mechanics.
|
// Generic helper functions not directly attached to Card Sim Engine mechanics.
|
||||||
|
|
||||||
@ -20,3 +26,46 @@ func Mean(vals ...float64) float64 {
|
|||||||
}
|
}
|
||||||
return total / float64(len(vals))
|
return total / float64(len(vals))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clamp returns the middle of the three provided values. It doesn't
|
||||||
|
// matter which order the values are in. This function is known as `mid` in
|
||||||
|
// Pico-8's library, among others. It is usually used to clamp a value to a
|
||||||
|
// range, and it doesn't care which order the range is written in.
|
||||||
|
func clamp[T constraints.Ordered](a, b, c T) T {
|
||||||
|
if a <= b && a <= c {
|
||||||
|
// `a` is least; mid is lower of `b` or `c`
|
||||||
|
if b <= c {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if a >= b && a >= c {
|
||||||
|
// `a` is most; mid is greater of `b` or `c`
|
||||||
|
if b >= c {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
// `a` is neither most nor least; therefore, `a` is mid
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrWrappedPanic = errors.New("panic")
|
||||||
|
|
||||||
|
// try catches a panic in the provided func and demotes it to an error, if any
|
||||||
|
// panic occurs. The returned error, if any, wraps `ErrWrappedPanic`. If the
|
||||||
|
// panic argument is itself an error, it is also wrapped; otherwise, it is
|
||||||
|
// stringified into the error message using `%v`.
|
||||||
|
func try(f func()) (finalErr error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if e, ok := r.(error); ok {
|
||||||
|
finalErr = fmt.Errorf("%w: %w", ErrWrappedPanic, e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
finalErr = fmt.Errorf("%w: %v", ErrWrappedPanic, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
f()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user