Compare commits
16 Commits
2480a1631b
...
v0.2
Author | SHA1 | Date | |
---|---|---|---|
99e372a4db
|
|||
3e34e25f54
|
|||
1464070339
|
|||
066ec431ff
|
|||
3455579be6
|
|||
5a7cb58707
|
|||
74a2493ef4
|
|||
2c2e68ff93
|
|||
d13e04e2f4
|
|||
57348f7ebf
|
|||
9796c2e970
|
|||
0f21020647
|
|||
e96d81a7b4
|
|||
3a7bf9c2fb
|
|||
00ea284cbc
|
|||
159f6b6b5f
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -21,3 +21,5 @@
|
|||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
|
# Visual studio Code exclusions
|
||||||
|
.vscode/settings.json
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Basic engine for NationStates-like "make decisions on issues" simulation games. Very incomplete.
|
Basic engine for NationStates-like "make decisions on issues" simulation games. Very incomplete.
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/git.chromaticdragon.app/kistaro/CardSimEngine)
|
||||||
|
|
||||||
## General turn model
|
## General turn model
|
||||||
|
|
||||||
1. Player has a hand of cards. Each card has one or more actions available.
|
1. Player has a hand of cards. Each card has one or more actions available.
|
||||||
|
@ -55,6 +55,9 @@ func Warningf(f string, args ...any) *Warning {
|
|||||||
// IsSeriousError returns whether e is a non-warning error. If e is nil, this
|
// IsSeriousError returns whether e is a non-warning error. If e is nil, this
|
||||||
// returns false.
|
// returns false.
|
||||||
func IsSeriousError(e error) bool {
|
func IsSeriousError(e error) bool {
|
||||||
|
if e == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return !errors.Is(e, AnyWarning)
|
return !errors.Is(e, AnyWarning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,3 +463,20 @@ func (p *Player[C]) ReportError(e error) {
|
|||||||
func (p *Player[C]) CanAct() bool {
|
func (p *Player[C]) CanAct() bool {
|
||||||
return p.ActionsRemaining > 0 && (len(p.Hand) > 0 || len(p.PermanentActions) > 0)
|
return p.ActionsRemaining > 0 && (len(p.Hand) > 0 || len(p.PermanentActions) > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug adds a message to the player's temporary messages if their debug level
|
||||||
|
// is at least the level specified.
|
||||||
|
func (p *Player[C]) Debug(minLevel int, msg Message) {
|
||||||
|
if p.DebugLevel < minLevel || msg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.TemporaryMessages = append(p.TemporaryMessages, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit adds a message to the player's temporary messages.
|
||||||
|
func (p *Player[C]) Emit(msg Message) {
|
||||||
|
if msg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.TemporaryMessages = append(p.TemporaryMessages, msg)
|
||||||
|
}
|
||||||
|
@ -141,7 +141,7 @@ func (r *RuleCollection[C]) performInsert(k *keyedRule[C]) {
|
|||||||
r.rules[k.id] = k
|
r.rules[k.id] = k
|
||||||
|
|
||||||
s := r.byStep[k.Step()]
|
s := r.byStep[k.Step()]
|
||||||
if s == nil {
|
if len(s) == 0 {
|
||||||
r.steps = nil
|
r.steps = nil
|
||||||
}
|
}
|
||||||
s = append(s, k.id)
|
s = append(s, k.id)
|
||||||
@ -326,7 +326,7 @@ func (r *RuleCollection[C]) Run(p *Player[C]) error {
|
|||||||
steps := r.steps
|
steps := r.steps
|
||||||
if steps == nil {
|
if steps == nil {
|
||||||
// Step set changed, recalculate.
|
// Step set changed, recalculate.
|
||||||
steps := make([]int, 0, len(r.byStep))
|
steps = make([]int, 0, len(r.byStep))
|
||||||
for step := range r.byStep {
|
for step := range r.byStep {
|
||||||
steps = append(steps, step)
|
steps = append(steps, step)
|
||||||
}
|
}
|
||||||
@ -334,18 +334,21 @@ func (r *RuleCollection[C]) Run(p *Player[C]) error {
|
|||||||
r.steps = steps
|
r.steps = steps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Debug(2, Msgf("Executing steps: %v", steps))
|
||||||
|
|
||||||
var errs ErrorCollector
|
var errs ErrorCollector
|
||||||
for _, step := range steps {
|
for _, step := range steps {
|
||||||
stepRules := r.byStep[step]
|
stepRules := r.byStep[step]
|
||||||
p.Rand.Shuffle(len(stepRules), func(i, j int) {
|
p.Debug(3, Msgf("Executing step %d; length %d", step, len(stepRules)))
|
||||||
stepRules[i], stepRules[j] = stepRules[j], stepRules[i]
|
ShuffleAll(stepRules, p.Rand)
|
||||||
})
|
|
||||||
var remove []RuleID
|
var remove []RuleID
|
||||||
halt := false
|
halt := false
|
||||||
for _, id := range stepRules {
|
for _, id := range stepRules {
|
||||||
rule := r.rules[id]
|
rule := r.rules[id]
|
||||||
|
p.Debug(4, Msgf("Executing rule %x (labeled %q)", id, rule.Label()))
|
||||||
err := rule.Enact(p)
|
err := rule.Enact(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
p.Debug(2, Msgf("Rule %x (%q): error: %v", id, rule.Label(), err))
|
||||||
ignore := false
|
ignore := false
|
||||||
if errors.Is(err, ErrDeleteRule) {
|
if errors.Is(err, ErrDeleteRule) {
|
||||||
remove = append(remove, id)
|
remove = append(remove, id)
|
||||||
@ -377,10 +380,14 @@ func (r *RuleCollection[C]) Run(p *Player[C]) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if halt {
|
if halt {
|
||||||
return errs.Emit()
|
ret := errs.Emit()
|
||||||
|
p.Debug(2, Msgf("Rules stopping early. Result: %v", ret))
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errs.Emit()
|
ret := errs.Emit()
|
||||||
|
p.Debug(2, Msgf("Rules complete. Result: %v", ret))
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleCollection[C]) applyDelayedUpdates() {
|
func (r *RuleCollection[C]) applyDelayedUpdates() {
|
||||||
|
240
cardsim/stats.go
240
cardsim/stats.go
@ -4,7 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A StatsCollection contains stats.
|
// A StatsCollection contains stats.
|
||||||
@ -114,9 +118,26 @@ func (s statFunc[T]) Visible() bool {
|
|||||||
return s.visible
|
return s.visible
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractStats pulls all exported Stat fields (not functions) out of a struct.
|
// ExtractStats pulls all exported stats out of a struct. It puts methods before
|
||||||
// If x cannot be resolved to a struct, it panics. It unwraps interfaces and
|
// fields. If the calculated name of a method conflicts with the calculated
|
||||||
// follows pointers to try to find a struct.
|
// name of a stat from a field, the method wins.
|
||||||
|
//
|
||||||
|
// A field is a stat if it is of some Stat type or is tagged with `cardsim:"stat"`,
|
||||||
|
// `cardsim:"hidden"` (invisible stat), `cardsim:"round2"` (or any integer, 2 is
|
||||||
|
// just an example), or `cardsim:"hiddenround3"`. `hiddenstat`, `statround`, and
|
||||||
|
// `hiddenstatround` are also accepted, but other orders of these directives
|
||||||
|
// are not. A "round" stat must be a float type and it will be rounded to
|
||||||
|
// this number of decimal places.
|
||||||
|
//
|
||||||
|
// A method is a Stat if it takes 0 arguments, returns exactly 1 value, and
|
||||||
|
// starts with Stat or HiddenStat.
|
||||||
|
//
|
||||||
|
// The name of these inferred stats is calculated by breaking the name into
|
||||||
|
// separate words before each capital letter, unless there are consecutive
|
||||||
|
// capital letters, which it interprets as an initialism (followed by the
|
||||||
|
// start of another word, if it's not at the end). To insert a space between
|
||||||
|
// consecutive capital letters, insert an underscore (`_`). This name inference
|
||||||
|
// trims "Stat" and "HiddenStat" off the front of method names.
|
||||||
func ExtractStats(x any) []Stat {
|
func ExtractStats(x any) []Stat {
|
||||||
v := reflect.ValueOf(x)
|
v := reflect.ValueOf(x)
|
||||||
for k := v.Kind(); k == reflect.Pointer || k == reflect.Interface; k = v.Kind() {
|
for k := v.Kind(); k == reflect.Pointer || k == reflect.Interface; k = v.Kind() {
|
||||||
@ -125,19 +146,122 @@ func ExtractStats(x any) []Stat {
|
|||||||
if v.Kind() != reflect.Struct {
|
if v.Kind() != reflect.Struct {
|
||||||
panic(fmt.Errorf("%T is not a struct", x))
|
panic(fmt.Errorf("%T is not a struct", x))
|
||||||
}
|
}
|
||||||
|
typ := v.Type()
|
||||||
|
|
||||||
var ret []Stat
|
var ret []Stat
|
||||||
lim := v.NumField()
|
|
||||||
|
known := make(map[string]bool)
|
||||||
|
for _, vv := range []reflect.Value{v, v.Addr()} {
|
||||||
|
xt := vv.Type()
|
||||||
|
lim := xt.NumMethod()
|
||||||
for i := 0; i < lim; i++ {
|
for i := 0; i < lim; i++ {
|
||||||
f := v.Field(i)
|
m := xt.Method(i)
|
||||||
|
if !m.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tm := m.Type
|
||||||
|
if tm.NumIn() != 1 {
|
||||||
|
// 1 arg -- receiver
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tm.NumOut() != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nameParts := explode(m.Name)
|
||||||
|
if len(nameParts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isHidden := false
|
||||||
|
if nameParts[0] == "Hidden" {
|
||||||
|
isHidden = true
|
||||||
|
nameParts = nameParts[1:]
|
||||||
|
}
|
||||||
|
if nameParts[0] != "Stat" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n := strings.Join(nameParts[1:], " ")
|
||||||
|
if n == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if known[n] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
known[n] = true
|
||||||
|
val := vv.Method(i).Call([]reflect.Value{})
|
||||||
|
if len(val) != 1 {
|
||||||
|
// This shouldn't happen - we already checked Out. Weird.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !val[0].CanInterface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, &StatLiteral{
|
||||||
|
Name: n,
|
||||||
|
Value: fmt.Sprint(val[0].Interface()),
|
||||||
|
IsVisible: !isHidden,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := reflect.VisibleFields(typ)
|
||||||
|
for _, sf := range fields {
|
||||||
|
if !sf.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f := v.FieldByIndex(sf.Index)
|
||||||
if !f.CanInterface() {
|
if !f.CanInterface() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
x := f.Interface()
|
iface := f.Interface()
|
||||||
if s, ok := x.(Stat); ok {
|
if s, ok := iface.(Stat); ok {
|
||||||
|
if known[s.StatName()] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
known[s.StatName()] = true
|
||||||
ret = append(ret, s)
|
ret = append(ret, s)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if t := sf.Tag.Get("cardsim"); t != "" {
|
||||||
|
isStat := false
|
||||||
|
isHidden := false
|
||||||
|
t = strings.ToLower(t)
|
||||||
|
t = strings.TrimSpace(t)
|
||||||
|
if strings.HasPrefix(t, "hidden") {
|
||||||
|
isStat = true
|
||||||
|
isHidden = true
|
||||||
|
t = t[6:]
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(t, "stat") {
|
||||||
|
isStat = true
|
||||||
|
t = t[4:]
|
||||||
|
}
|
||||||
|
var val string
|
||||||
|
if strings.HasPrefix(t, "round") {
|
||||||
|
isStat = true
|
||||||
|
t = t[5:]
|
||||||
|
n, _ := strconv.Atoi(t)
|
||||||
|
fs := fmt.Sprintf("%%.%df", n)
|
||||||
|
val = fmt.Sprintf(fs, iface)
|
||||||
|
} else if isStat {
|
||||||
|
val = fmt.Sprint(iface)
|
||||||
|
} else {
|
||||||
|
continue // not identifiably a stat
|
||||||
|
}
|
||||||
|
nm := strings.Join(explode(sf.Name), " ")
|
||||||
|
if known[nm] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
known[nm] = true
|
||||||
|
ret = append(ret, &StatLiteral{
|
||||||
|
Name: nm,
|
||||||
|
Value: val,
|
||||||
|
IsVisible: !isHidden,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Else, not a stat.
|
||||||
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,3 +297,105 @@ func (s statSorter) Less(i, j int) bool {
|
|||||||
// Names differ only by capitalization, if that.
|
// Names differ only by capitalization, if that.
|
||||||
return lhs.StatName() < rhs.StatName()
|
return lhs.StatName() < rhs.StatName()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StatLiteral stores a ready-to-emit stat value.
|
||||||
|
type StatLiteral struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
IsVisible bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatLiteral) StatName() string {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatLiteral) String() string {
|
||||||
|
return s.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatLiteral) Visible() bool {
|
||||||
|
return s.IsVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmitStat(name string, v any) *StatLiteral {
|
||||||
|
return &StatLiteral{
|
||||||
|
Name: name,
|
||||||
|
Value: fmt.Sprint(v),
|
||||||
|
IsVisible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmitHiddenStat(name string, v any) *StatLiteral {
|
||||||
|
return &StatLiteral{
|
||||||
|
Name: name,
|
||||||
|
Value: fmt.Sprint(v),
|
||||||
|
IsVisible: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Statf(name string, f string, args ...any) *StatLiteral {
|
||||||
|
return &StatLiteral{
|
||||||
|
Name: name,
|
||||||
|
Value: fmt.Sprintf(f, args...),
|
||||||
|
IsVisible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HiddentStatf(name string, f string, args ...any) *StatLiteral {
|
||||||
|
return &StatLiteral{
|
||||||
|
Name: name,
|
||||||
|
Value: fmt.Sprintf(f, args...),
|
||||||
|
IsVisible: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RoundStat[N constraints.Float](name string, val N, decimals int) *StatLiteral {
|
||||||
|
f := fmt.Sprintf("%%.%df", decimals)
|
||||||
|
return &StatLiteral{
|
||||||
|
Name: name,
|
||||||
|
Value: fmt.Sprintf(f, val),
|
||||||
|
IsVisible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RoundHiddenStat[N constraints.Float](name string, val N, decimals int) *StatLiteral {
|
||||||
|
r := RoundStat(name, val, decimals)
|
||||||
|
r.IsVisible = false
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// explode turns CamelCase into multiple strings. It recognizes initialisms. To
|
||||||
|
// split consecutive capital letters into separate words instead of recognizing
|
||||||
|
// them as an initialism, insert underscores.
|
||||||
|
func explode(s string) []string {
|
||||||
|
var parts []string
|
||||||
|
started := 0
|
||||||
|
initialism := false
|
||||||
|
for i, r := range s {
|
||||||
|
if unicode.IsUpper(r) {
|
||||||
|
if initialism || (started == i) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if started == i-1 {
|
||||||
|
initialism = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts = append(parts, s[started:i])
|
||||||
|
started = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r == '_' {
|
||||||
|
parts = append(parts, s[started:i])
|
||||||
|
initialism = false
|
||||||
|
started = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if initialism {
|
||||||
|
parts = append(parts, s[started:i-1])
|
||||||
|
initialism = false
|
||||||
|
started = i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts = append(parts, s[started:])
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
@ -8,13 +8,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func RunSimpleTerminalUI[C StatsCollection](p *Player[C]) error {
|
func RunSimpleTerminalUI[C StatsCollection](p *Player[C]) error {
|
||||||
for {
|
|
||||||
err := p.StartNextTurn()
|
err := p.StartNextTurn()
|
||||||
if p.DebugLevel < 1 && IsSeriousError(err) {
|
if p.DebugLevel < 1 && IsSeriousError(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.ReportError(err)
|
p.ReportError(err)
|
||||||
|
|
||||||
|
for {
|
||||||
for p.CanAct() {
|
for p.CanAct() {
|
||||||
isCard, cardIdx, choiceIdx, err := pickNextAction(p)
|
isCard, cardIdx, choiceIdx, err := pickNextAction(p)
|
||||||
p.ReportError(err)
|
p.ReportError(err)
|
||||||
@ -135,7 +136,7 @@ func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int,
|
|||||||
cls()
|
cls()
|
||||||
displayOnePanel(p, p.InfoPanels[i-1])
|
displayOnePanel(p, p.InfoPanels[i-1])
|
||||||
wait()
|
wait()
|
||||||
} else if i < handOffset {
|
} else if i <= handOffset {
|
||||||
i = i - actionsOffset - 1
|
i = i - actionsOffset - 1
|
||||||
option, promptErr := promptCard(p, p.PermanentActions[i])
|
option, promptErr := promptCard(p, p.PermanentActions[i])
|
||||||
if option >= 0 || IsSeriousError(promptErr) {
|
if option >= 0 || IsSeriousError(promptErr) {
|
||||||
@ -143,7 +144,7 @@ func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
i = i - handOffset - 1
|
i = i - handOffset - 1
|
||||||
option, promptErr := promptCard(p, p.Hand[i-handOffset-1])
|
option, promptErr := promptCard(p, p.Hand[i])
|
||||||
if option >= 0 || IsSeriousError(promptErr) {
|
if option >= 0 || IsSeriousError(promptErr) {
|
||||||
return true, i, option, nil
|
return true, i, option, nil
|
||||||
}
|
}
|
||||||
@ -205,14 +206,11 @@ func displayMessageSection[C StatsCollection](p *Player[C]) bool {
|
|||||||
if len(p.TemporaryMessages) == 0 {
|
if len(p.TemporaryMessages) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
hasPrevious := false
|
|
||||||
for _, m := range p.TemporaryMessages {
|
for _, m := range p.TemporaryMessages {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
if hasPrevious {
|
|
||||||
lightDivider()
|
|
||||||
}
|
|
||||||
display(m)
|
display(m)
|
||||||
hasPrevious = true
|
} else {
|
||||||
|
fmt.Println()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -265,7 +263,7 @@ func promptCard[C StatsCollection](p *Player[C], card Card[C]) (optionIdx int, e
|
|||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
if valid {
|
if valid {
|
||||||
fmt.Printf("Go (B)ack, (Q)uit, or enact a choice (1 - %d)? > ", len(opts)+1)
|
fmt.Printf("Go (B)ack, (Q)uit, or enact a choice (1 - %d)? > ", len(opts))
|
||||||
} else {
|
} else {
|
||||||
fmt.Print("Go (B)ack or (Q)uit? > ")
|
fmt.Print("Go (B)ack or (Q)uit? > ")
|
||||||
}
|
}
|
||||||
@ -374,6 +372,7 @@ func statsMode[C StatsCollection](p *Player[C]) error {
|
|||||||
} else if v <= 0 || v > n {
|
} else if v <= 0 || v > n {
|
||||||
fmt.Println("There's no info panel with that index.")
|
fmt.Println("There's no info panel with that index.")
|
||||||
} else {
|
} else {
|
||||||
|
cls()
|
||||||
err := displayOnePanel(p, p.InfoPanels[v-1])
|
err := displayOnePanel(p, p.InfoPanels[v-1])
|
||||||
errs.Add(err)
|
errs.Add(err)
|
||||||
if IsSeriousError(err) {
|
if IsSeriousError(err) {
|
||||||
@ -507,7 +506,7 @@ func review[C StatsCollection](p *Player[C]) error {
|
|||||||
cls()
|
cls()
|
||||||
displayOnePanel(p, p.InfoPanels[i-1])
|
displayOnePanel(p, p.InfoPanels[i-1])
|
||||||
wait()
|
wait()
|
||||||
} else if i < handOffset {
|
} else if i <= handOffset {
|
||||||
i = i - actionsOffset - 1
|
i = i - actionsOffset - 1
|
||||||
_, _, err := displayCard(p, p.PermanentActions[i], false)
|
_, _, err := displayCard(p, p.PermanentActions[i], false)
|
||||||
errs.Add(err)
|
errs.Add(err)
|
||||||
|
10
go.mod
10
go.mod
@ -1,3 +1,11 @@
|
|||||||
module cardSimEngine
|
module git.chromaticdragon.app/kistaro/CardSimEngine
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
|
require github.com/kr/pretty v0.3.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||||
|
)
|
||||||
|
10
go.sum
Normal file
10
go.sum
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||||
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
@ -1,9 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cardSimEngine/cardsim"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.chromaticdragon.app/kistaro/CardSimEngine/cardsim"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type aliases, unlike distinctly named types, are fully substitutable for
|
// Type aliases, unlike distinctly named types, are fully substitutable for
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cardSimEngine/cardsim"
|
"git.chromaticdragon.app/kistaro/CardSimEngine/cardsim"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SmokeTestCollection is a stats collection for the simple test sim.
|
// SmokeTestCollection is a stats collection for the simple test sim.
|
||||||
type SmokeTestCollection struct {
|
type SmokeTestCollection struct {
|
||||||
Number cardsim.Stored[int]
|
Number cardsim.Stored[int]
|
||||||
Total cardsim.Stored[int]
|
Total cardsim.Stored[int64]
|
||||||
Turns cardsim.Invisible[int]
|
Turns cardsim.Invisible[int]
|
||||||
|
|
||||||
Flavor cardsim.Stored[string]
|
Flavor cardsim.Stored[string]
|
||||||
|
|
||||||
|
Things int `cardsim:"stat"`
|
||||||
|
MoreThings int `cardsim:"hidden"`
|
||||||
|
FloatyThings float64 `cardsim:"round1"`
|
||||||
|
Label string `cardsim:"stat"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SmokeTestCollection) Average() float64 {
|
func (c *SmokeTestCollection) Average() float64 {
|
||||||
@ -23,3 +28,7 @@ func (c *SmokeTestCollection) Stats() []cardsim.Stat {
|
|||||||
cardsim.SortStats(stats)
|
cardsim.SortStats(stats)
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *SmokeTestCollection) StatTotalThings() float64 {
|
||||||
|
return float64(c.Things+c.MoreThings) + c.FloatyThings
|
||||||
|
}
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cardSimEngine/cardsim"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.chromaticdragon.app/kistaro/CardSimEngine/cardsim"
|
||||||
|
|
||||||
|
"github.com/kr/pretty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -25,6 +28,10 @@ func main() {
|
|||||||
Name: "Flavor",
|
Name: "Flavor",
|
||||||
Value: "Lemon",
|
Value: "Lemon",
|
||||||
},
|
},
|
||||||
|
Things: 5,
|
||||||
|
MoreThings: 9,
|
||||||
|
FloatyThings: 123.456,
|
||||||
|
Label: "whee",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
p.Name = "Dave"
|
p.Name = "Dave"
|
||||||
@ -38,6 +45,7 @@ func main() {
|
|||||||
Name: cardsim.MsgStr("Stats"),
|
Name: cardsim.MsgStr("Stats"),
|
||||||
Intro: cardsim.MsgStr("Hi! These are the smoke test stats."),
|
Intro: cardsim.MsgStr("Hi! These are the smoke test stats."),
|
||||||
},
|
},
|
||||||
|
ruledumper{},
|
||||||
}
|
}
|
||||||
p.Prompt = prompt{}
|
p.Prompt = prompt{}
|
||||||
p.DebugLevel = 5
|
p.DebugLevel = 5
|
||||||
@ -54,9 +62,22 @@ func main() {
|
|||||||
type prompt struct{}
|
type prompt struct{}
|
||||||
|
|
||||||
func (prompt) Title(p *cardsim.Player[*SmokeTestCollection]) cardsim.Message {
|
func (prompt) Title(p *cardsim.Player[*SmokeTestCollection]) cardsim.Message {
|
||||||
return cardsim.MsgStr("Prompt title -- should not be visible?")
|
return cardsim.MsgStr("Smoke Test")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (prompt) Info(p *cardsim.Player[*SmokeTestCollection]) ([]cardsim.Message, error) {
|
func (prompt) Info(p *cardsim.Player[*SmokeTestCollection]) ([]cardsim.Message, error) {
|
||||||
return []cardsim.Message{cardsim.MsgStr("Here, have some stuff.")}, nil
|
return []cardsim.Message{
|
||||||
|
cardsim.MsgStr("Here, have some stuff."),
|
||||||
|
cardsim.Msgf("It's turn %d according to the player and turn %d according to me.", p.TurnNumber, p.Stats.Turns.Value),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ruledumper struct{}
|
||||||
|
|
||||||
|
func (ruledumper) Title(p *player) cardsim.Message {
|
||||||
|
return cardsim.MsgStr("Rule Dumper")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ruledumper) Info(p *player) ([]cardsim.Message, error) {
|
||||||
|
return []cardsim.Message{cardsim.Msgf("%# v", pretty.Formatter(p.Rules))}, nil
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cardSimEngine/cardsim"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"git.chromaticdragon.app/kistaro/CardSimEngine/cardsim"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
Reference in New Issue
Block a user