Compare commits

..

5 Commits

Author SHA1 Message Date
37d3b639bf
Merge branch 'Rewrite-Stats-System' of https://git.chromaticdragon.app/kistaro/KoboldSim into effects_table
# Conflicts:
#	koboldsim/cards.go
#	koboldsim/stats.go

cards.go -- Rakeela fixed a card differently from how I did
stats.go -- expected; Rakeela added to the big switch statement I got rid of
2024-09-29 12:51:39 -07:00
a237fa81bf
pull try out of Add
deferring a recovery handler is normal in Go but opaque. Pulling out a helper for this (`try`) makes it more obvious that this is just another "if this operation fails, return error" case.
2024-09-29 12:41:49 -07:00
68cab7d2be
Remove stale "Add case" snippet
The reflection-driven implementation of Add no longer contains a huge boilerplate `switch` statement so we no longer need a snippet for the boilerplate.
2024-09-29 12:20:53 -07:00
2d16f97314
Remiplement Add to use reflection.
Now it matters whether `FieldLabel`s are spelled correctly, but in return, we don't have to write and maintain that obnoxious `switch` statement.
2024-09-29 12:19:07 -07:00
8a28f38d4d
Finish converting second option of Pan-Tribal Festival of Bureaucracy. 2024-09-29 12:18:12 -07:00
4 changed files with 66 additions and 93 deletions

View File

@ -15,13 +15,4 @@
// ], // ],
// "description": "Log output to console" // "description": "Log output to console"
// } // }
"KoboldMine Add case": {
"scope": "go",
"prefix": "case",
"body": [
"case ${1:field}:",
"\tk.${1:field} += amount",
"\treturn nil",
],
}
} }

View File

@ -9,7 +9,6 @@ import (
var ( var (
ErrOptionNotEnabled = errors.New("option not enabled") ErrOptionNotEnabled = errors.New("option not enabled")
ErrPolicyNotEnacted = errors.New("cannot unenact policy that is not enacted") 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 // ErrUnimplemented and ErrKeepMessaage are "non-errors". They are used
// as special signals that the result needs to be handled in a special way; // as special signals that the result needs to be handled in a special way;

View File

@ -1,7 +1,9 @@
package koboldsim package koboldsim
import ( import (
"errors"
"fmt" "fmt"
"reflect"
"git.chromaticdragon.app/kistaro/CardSimEngine/cardsim" "git.chromaticdragon.app/kistaro/CardSimEngine/cardsim"
) )
@ -167,6 +169,10 @@ func NewKoboldMine() *KoboldMine {
} }
} }
// 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 type FieldLabel string
const ( const (
@ -199,89 +205,44 @@ const (
Welfare FieldLabel = "Welfare" 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 { func (k *KoboldMine) Add(which FieldLabel, amount float64) error {
switch which { kv := reflect.ValueOf(k).Elem()
case Alchemy: f := kv.FieldByName(string(which))
k.Alchemy += amount if !f.IsValid() {
return nil return fmt.Errorf("cannot add %f to field %q: %w", amount, which, ErrNoFieldLabel)
case Authoritarianism: }
k.Authoritarianism += amount if !f.CanFloat() {
return nil return fmt.Errorf("cannot add %f to field %q: %w", amount, which, ErrFieldNotFloat)
case BasePopulation: }
k.BasePopulation += amount
return nil if err := try(func() { f.SetFloat(f.Float() + amount) }); err != nil {
case Bureaucracy: return fmt.Errorf("could not add %f to field %q: %w", amount, which, err)
k.Bureaucracy += amount }
return nil
case Construction:
k.Construction += amount
return nil
case Cruelty:
k.Cruelty += amount
return nil
case Education:
k.Education += amount
return nil
case FoodSupply:
k.FoodSupply += amount
return nil
case ForeignRelations:
k.ForeignRelations += amount
return nil
case ForeignRelExpense:
k.ForeignRelExpense += amount
return nil
case Gadgetry:
k.Gadgetry += amount
return nil
case Greed:
k.Greed += amount
return nil
case Gullibility:
k.Gullibility += amount
return nil
case Healthcare:
k.Healthcare += amount
return nil
case HiddenRelPenalty:
k.HiddenRelPenalty += amount
return nil
case Hospitality:
k.Hospitality += amount
return nil
case Logistics:
k.Logistics += amount
return nil
case Madness:
k.Madness += amount
return nil
case Manufacturing:
k.Manufacturing += amount
return nil
case Militarism:
k.Militarism += amount
return nil
case Mining:
k.Mining += amount
return nil
case ParksExpense:
k.ParksExpense += amount
return nil
case Publishing:
k.Publishing += amount
return nil
case Rebellion:
k.Rebellion += amount
return nil
case Scavenging:
k.Scavenging += amount
return nil
case Secrecy:
k.Secrecy += amount
return nil
case Welfare:
k.Welfare += amount
return nil return nil
} }
return fmt.Errorf("cannot add %f to %q: %w", amount, which, ErrNoFieldLabel)
}

View File

@ -1,6 +1,8 @@
package koboldsim package koboldsim
import ( import (
"errors"
"fmt"
"math" "math"
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
@ -47,3 +49,23 @@ func clamp[T constraints.Ordered](a, b, c T) T {
// `a` is neither most nor least; therefore, `a` is mid // `a` is neither most nor least; therefore, `a` is mid
return a 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
}