summaryrefslogtreecommitdiff
path: root/stats
diff options
context:
space:
mode:
authorewy <ewy0@protonmail.com>2026-05-22 16:54:49 +0200
committerewy <ewy0@protonmail.com>2026-05-22 16:54:49 +0200
commit007e2de369f9fc26da3237646de14f2af5052ee8 (patch)
treef81557385628fc93f1ef9616b8bc75a304f9d740 /stats
initial commit
Diffstat (limited to 'stats')
-rw-r--r--stats/acts.go33
-rw-r--r--stats/ancients.go50
-rw-r--r--stats/cards.go37
-rw-r--r--stats/count.go33
-rw-r--r--stats/db.go44
-rw-r--r--stats/enrich.go71
-rw-r--r--stats/option.go11
-rw-r--r--stats/versions_enrich.go23
8 files changed, 302 insertions, 0 deletions
diff --git a/stats/acts.go b/stats/acts.go
new file mode 100644
index 0000000..19c4219
--- /dev/null
+++ b/stats/acts.go
@@ -0,0 +1,33 @@
+package stats
+
+import (
+ "slices"
+ "strings"
+ "sts2stats/model"
+)
+
+type Act struct {
+ Index int
+ Label string
+ Key string `db:"Key,primarykey"`
+}
+
+var actKeys []string
+
+func EnrichActs(run model.Run, stat RunStat) (result []any, err error) {
+ for i, a := range run.Acts {
+ if slices.Contains(actKeys, a) {
+ continue
+ }
+ actKeys = append(actKeys, a)
+
+ act := Act{
+ Index: i,
+ Key: a,
+ Label: strings.SplitN(a, ".", 2)[0],
+ }
+
+ result = append(result, &act)
+ }
+ return result, nil
+}
diff --git a/stats/ancients.go b/stats/ancients.go
new file mode 100644
index 0000000..16ea207
--- /dev/null
+++ b/stats/ancients.go
@@ -0,0 +1,50 @@
+package stats
+
+import (
+ "sts2stats/model"
+)
+
+const (
+ AncientKey = "ancient"
+)
+
+type AncientChoice struct {
+ RunStat
+ AncientOption
+ ActIndex int
+ ActName string
+ Chosen bool
+}
+
+type AncientOption struct {
+ Key string
+ Type string
+}
+
+func EnrichAncients(run model.Run, st RunStat) (opts []any, err error) {
+ for actIndex, act := range run.MapPointHistory {
+ for _, floor := range act {
+ if floor.MapPointType != AncientKey {
+ continue
+ }
+
+ for _, stat := range floor.PlayerStats {
+ for _, choice := range stat.AncientChoice {
+
+ opts = append(opts, &AncientChoice{
+ AncientOption: AncientOption{
+ Key: choice.TextKey,
+ Type: choice.Title.Table,
+ },
+ RunStat: st,
+ ActIndex: actIndex,
+ ActName: run.Acts[actIndex],
+ Chosen: choice.WasChosen,
+ })
+ }
+ }
+
+ }
+ }
+ return opts, nil
+}
diff --git a/stats/cards.go b/stats/cards.go
new file mode 100644
index 0000000..d4049f4
--- /dev/null
+++ b/stats/cards.go
@@ -0,0 +1,37 @@
+package stats
+
+import (
+ "sts2stats/model"
+)
+
+type CardChoice struct {
+ RunStat
+ model.PlayerStat
+ RoomStat
+ Card string
+ Upgrade int
+ Picked bool
+}
+
+func EnrichCardChoice(run model.Run, st RunStat) (result []any, err error) {
+ for ia, act := range run.MapPointHistory {
+ for i, floor := range act {
+ for _, stat := range floor.PlayerStats {
+ for _, choice := range stat.CardChoices {
+ result = append(result, &CardChoice{
+ RunStat: st,
+ Card: choice.Card.Id,
+ Upgrade: choice.Card.CurrentUpgradeLevel,
+ PlayerStat: stat.PlayerStat,
+ RoomStat: RoomStat{
+ Floor: i,
+ Act: ia + 1,
+ },
+ Picked: false,
+ })
+ }
+ }
+ }
+ }
+ return
+}
diff --git a/stats/count.go b/stats/count.go
new file mode 100644
index 0000000..e9c87f5
--- /dev/null
+++ b/stats/count.go
@@ -0,0 +1,33 @@
+package stats
+
+import "sts2stats/model"
+
+type Counter interface {
+ Count(run model.Run) error
+}
+
+var Counters = []Counter{}
+
+type CountFunc func(run model.Run) error
+
+type CountFuncWrapper struct {
+ f CountFunc
+}
+
+func (c CountFuncWrapper) Count(run model.Run) error {
+ return c.f(run)
+}
+
+func Func(f CountFunc) Counter {
+ return CountFuncWrapper{f: f}
+}
+
+func Count(run model.Run) error {
+ for _, c := range Counters {
+ err := c.Count(run)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/stats/db.go b/stats/db.go
new file mode 100644
index 0000000..327373f
--- /dev/null
+++ b/stats/db.go
@@ -0,0 +1,44 @@
+package stats
+
+import (
+ "sts2stats/model"
+ "time"
+)
+
+type RunStat struct {
+ RunId string
+ StartTime time.Time
+ Ascension int
+ Version string
+ Win bool
+ FloorsClimbed int
+ Abandoned bool
+ InProgress bool
+}
+
+type RoomStat struct {
+ Act int
+ Floor int
+}
+
+func NewRunStat(run model.Run) RunStat {
+ st := RunStat{
+ Version: run.BuildID,
+ StartTime: time.Unix(int64(run.StartTime), 0),
+ Ascension: run.Ascension,
+ Win: run.Win,
+ RunId: run.RunId,
+ Abandoned: run.WasAbandoned,
+ FloorsClimbed: runLen(run),
+ InProgress: run.KilledByEncounter != "" || run.KilledByEvent != "" || run.Win != true,
+ }
+ return st
+}
+
+func runLen(run model.Run) int {
+ var res int
+ for _, a := range run.MapPointHistory {
+ res += len(a)
+ }
+ return res
+}
diff --git a/stats/enrich.go b/stats/enrich.go
new file mode 100644
index 0000000..40e91ec
--- /dev/null
+++ b/stats/enrich.go
@@ -0,0 +1,71 @@
+package stats
+
+import (
+ "sts2stats/model"
+ "sts2stats/spool"
+ "sts2stats/storage"
+ "sync"
+ "time"
+)
+
+type LoadFunc = func() error
+
+// Enricher reads data from the RunSave and fills dictionaries and data structures
+type Enricher interface {
+ Enrich(run model.Run, stat RunStat) ([]any, error)
+}
+
+type EnrichFunc func(run model.Run, stat RunStat) ([]any, error)
+
+type EnrichFuncWrapper struct {
+ f EnrichFunc
+}
+
+func EnrichWrap(f EnrichFunc) Enricher {
+ return EnrichFuncWrapper{f: f}
+}
+
+func (e EnrichFuncWrapper) Enrich(run model.Run, stat RunStat) ([]any, error) {
+ return e.f(run, stat)
+}
+
+var Enrichers = map[string]Enricher{
+ "act": EnrichWrap(EnrichActs),
+ "version": EnrichWrap(EnrichGameVersion),
+ "ancient choice": EnrichWrap(EnrichAncients),
+ "card choice": EnrichWrap(EnrichCardChoice),
+}
+
+func Enrich(run model.Run) error {
+ startTime := time.Now()
+ id := run.RunId[:4]
+ wg := sync.WaitGroup{}
+ st := NewRunStat(run)
+ for k, e := range Enrichers {
+ wg.Go(func() {
+ spool.Debug("[%v] Starting %v enrichment\n", id, k)
+ res, err := e.Enrich(run, st)
+ if err != nil {
+ spool.Panic("%v\n", err)
+ }
+
+ if len(res) == 0 {
+ spool.Debug("[%v] Finished %v enrichment\n", id, k)
+ return
+ }
+
+ spool.Debug("[%v] Collected %v entities (%v)\n", id, k, len(res))
+ err = storage.SaveNow(res...)
+ if err != nil {
+ spool.Panic("during %v: %v\n", k, err)
+ }
+ spool.Debug("[%v] Saved %v entities (%v)\n", id, k, len(res))
+ })
+ }
+ wg.Wait()
+
+ endTime := time.Now()
+ spool.Info("[%v] digested run\n", id)
+ spool.Debug("[%v] took %.2fs", endTime.Sub(startTime).Seconds())
+ return nil
+}
diff --git a/stats/option.go b/stats/option.go
new file mode 100644
index 0000000..e8d729d
--- /dev/null
+++ b/stats/option.go
@@ -0,0 +1,11 @@
+package stats
+
+type ChoiceStat struct {
+ Amount int
+ Wins int
+}
+
+type Choice struct {
+ Taken *ChoiceStat
+ Ignored *ChoiceStat
+}
diff --git a/stats/versions_enrich.go b/stats/versions_enrich.go
new file mode 100644
index 0000000..416c469
--- /dev/null
+++ b/stats/versions_enrich.go
@@ -0,0 +1,23 @@
+package stats
+
+import (
+ "slices"
+ "sts2stats/model"
+)
+
+type GameVersion struct {
+ Version string `db:"Version,primarykey"`
+}
+
+var versions []string
+
+func EnrichGameVersion(run model.Run, stat RunStat) (result []any, err error) {
+ if !slices.Contains(versions, run.BuildID) {
+ versions = append(versions, run.BuildID)
+ v := GameVersion{
+ Version: run.BuildID,
+ }
+ result = append(result, &v)
+ }
+ return result, nil
+}