174 lines
4.0 KiB
Go
174 lines
4.0 KiB
Go
|
package cardsim
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// A StatsCollection contains stats.
|
||
|
type StatsCollection interface {
|
||
|
// Stats returns all the stats in this collection. It's okay for
|
||
|
// these to be copies rather than pointers. Stats will be presented
|
||
|
// to the player in this order.
|
||
|
Stats() []Stat
|
||
|
}
|
||
|
|
||
|
// A Stat is some value that can be printed as part of player status.
|
||
|
// It may not be an actual stored value -- it might only be calculated.
|
||
|
type Stat interface {
|
||
|
// StatName returns the name of this stat, as displayed to the player.
|
||
|
StatName() string
|
||
|
// String prints the value of the stat.
|
||
|
String() string // compatible with fmt.Stringer
|
||
|
// Visible returns whether this stat should be displayed to the player
|
||
|
// during regular gameplay. (Invisible stats are important for debugging.)
|
||
|
Visible() bool
|
||
|
}
|
||
|
|
||
|
// Stored is a generic Stat implementation that stores a stat value and name.
|
||
|
// It's visible to the player.
|
||
|
type Stored[T any] struct {
|
||
|
// Display name of this Stat.
|
||
|
Name string
|
||
|
// Value of this Stat. Can be overwritten.
|
||
|
Value T
|
||
|
}
|
||
|
|
||
|
// Statname implements Stat.
|
||
|
func (s Stored[T]) StatName() string {
|
||
|
return s.Name
|
||
|
}
|
||
|
|
||
|
// String implements Stat and fmt.Stringer.
|
||
|
func (s Stored[T]) String() string {
|
||
|
return fmt.Sprint(s.Value)
|
||
|
}
|
||
|
|
||
|
// Visible implements Stat.
|
||
|
func (Stored[T]) Visible() bool {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Invisible is a generic Stat implementation that stores a stat value and name.
|
||
|
// It's not visible to the player.
|
||
|
type Invisible[T any] struct {
|
||
|
// Display name of this Stat.
|
||
|
Name string
|
||
|
// Value of this Stat. Can be overwritten.
|
||
|
Value T
|
||
|
}
|
||
|
|
||
|
// Statname implements Stat.
|
||
|
func (i Invisible[T]) StatName() string {
|
||
|
return i.Name
|
||
|
}
|
||
|
|
||
|
// String implements Stat and fmt.Stringer.
|
||
|
func (i Invisible[T]) String() string {
|
||
|
return fmt.Sprint(i.Value)
|
||
|
}
|
||
|
|
||
|
// Visible implements Stat.
|
||
|
func (Invisible[T]) Visible() bool {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// StatFunc names a function as a stat visible to the player.
|
||
|
func StatFunc[T any](name string, f func() T) Stat {
|
||
|
return statFunc[T]{
|
||
|
f: f,
|
||
|
name: name,
|
||
|
visible: true,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// InvisibleStatFunc names a function as a stat not visible to the player.
|
||
|
func InvisibleStatFunc[T any](name string, f func() T) Stat {
|
||
|
return statFunc[T]{
|
||
|
f: f,
|
||
|
name: name,
|
||
|
visible: false,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// statFunc wraps a function as a stat.
|
||
|
type statFunc[T any] struct {
|
||
|
f func() T
|
||
|
name string
|
||
|
visible bool
|
||
|
}
|
||
|
|
||
|
func (s statFunc[T]) StatName() string {
|
||
|
return s.name
|
||
|
}
|
||
|
|
||
|
func (s statFunc[T]) String() string {
|
||
|
return fmt.Sprint(s.f())
|
||
|
}
|
||
|
|
||
|
func (s statFunc[T]) Visible() bool {
|
||
|
return s.visible
|
||
|
}
|
||
|
|
||
|
// ExtractStats pulls all exported Stat fields (not functions) out of a struct.
|
||
|
// If x cannot be resolved to a struct, it panics. It unwraps interfaces and
|
||
|
// follows pointers to try to find a struct.
|
||
|
func ExtractStats(x any) []Stat {
|
||
|
v := reflect.ValueOf(x)
|
||
|
for k := v.Kind(); k == reflect.Pointer || k == reflect.Interface; k = v.Kind() {
|
||
|
v = v.Elem()
|
||
|
}
|
||
|
if v.Kind() != reflect.Struct {
|
||
|
panic(fmt.Errorf("%T is not a struct", x))
|
||
|
}
|
||
|
|
||
|
var ret []Stat
|
||
|
lim := v.NumField()
|
||
|
for i := 0; i < lim; i++ {
|
||
|
f := v.Field(i)
|
||
|
if !f.CanInterface() {
|
||
|
continue
|
||
|
}
|
||
|
x := f.Interface()
|
||
|
if s, ok := x.(Stat); ok {
|
||
|
ret = append(ret, s)
|
||
|
}
|
||
|
}
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
// SortStats sorts the provided slice of stats in place. It puts all visible
|
||
|
// stats before all invisible stats, then alphabetizes (case-insensitive).
|
||
|
func SortStats(ss []Stat) {
|
||
|
sort.Sort(statSorter(ss))
|
||
|
}
|
||
|
|
||
|
// statSorter implements sort.Interface for []Stat.
|
||
|
type statSorter []Stat
|
||
|
|
||
|
// Len implements sort.Interface.
|
||
|
func (s statSorter) Len() int {
|
||
|
return len(s)
|
||
|
}
|
||
|
|
||
|
// Swap implements sort.Interface.
|
||
|
func (s statSorter) Swap(i, j int) {
|
||
|
s[i], s[j] = s[j], s[i]
|
||
|
}
|
||
|
|
||
|
// Less implements sort.Interface.
|
||
|
func (s statSorter) Less(i, j int) bool {
|
||
|
lhs, rhs := s[i], s[j]
|
||
|
if lhs.Visible() != rhs.Visible() {
|
||
|
return lhs.Visible()
|
||
|
}
|
||
|
ln, rn := strings.ToLower(lhs.StatName()), strings.ToLower(rhs.StatName())
|
||
|
if ln != rn {
|
||
|
return ln < rn
|
||
|
}
|
||
|
// Names differ only by capitalization, if that.
|
||
|
return lhs.StatName() < rhs.StatName()
|
||
|
}
|