~hokiegeek/bgg-play-stats

d883bd1f6b9570d1fdc7153ecc66b1da8007fb45 — HokieGeek 11 months ago a6ec970
fleshing out a way to display knight combinations
A .gitignore => .gitignore +1 -0
@@ 0,0 1,1 @@
cmd/astro-knights-play-stats/*.json

M cmd/astro-knights-play-stats/main.go => cmd/astro-knights-play-stats/main.go +37 -16
@@ 1,7 1,9 @@
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"



@@ 112,27 114,46 @@ func (m model) View() string {
}

func main() {
	username := "HokieGeek"
	var err error
	jsonf, err := os.ReadFile(os.Args[1])
	if err != nil {
		log.Fatal(err)
	}

	// retrieve the plays
	fmt.Printf("retrieving plays for %s ...\n", username)
	plays, err := astro_knights.GetPlaysByUsername(username)
	var collection astro_knights.UserCollection
	err = json.Unmarshal(jsonf, &collection)
	if err != nil {
		panic(err)
		log.Fatal("error during Unmarshal(): ", err)
	}

	m := model{
		tabs: []string{"plays", "knights", "bosses", "homeworlds"},
		tabContent: []tea.Model{
			newTabPlays(plays),
			newTabKnights(plays),
			newTabBosses(plays),
			newTabHomeworlds(plays),
		},
	// retrieve the plays
	fmt.Printf("retrieving plays for %s ...\n", collection.BggUsername)
	collection.Plays, err = astro_knights.GetPlaysByUsername(collection.BggUsername)
	if err != nil {
		log.Panic(err)
	}

	if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {
		fmt.Println("WTF?:", err)
		os.Exit(1)
	stats, err := astro_knights.GetKnightUseStats(collection)
	if err != nil {
		log.Panic(err)
	}

	fmt.Println(stats)

	/*
		m := model{
			tabs: []string{"plays", "knights", "bosses", "homeworlds"},
			tabContent: []tea.Model{
				newTabPlays(plays),
				newTabKnights(plays),
				newTabBosses(plays),
				newTabHomeworlds(plays),
			},
		}

		if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {
			fmt.Println("WTF?:", err)
			os.Exit(1)
		}
	*/
}

M cmd/astro-knights-play-stats/tab_knights.go => cmd/astro-knights-play-stats/tab_knights.go +10 -5
@@ 8,7 8,7 @@ import (
)

type tabKnightsModel struct {
	// playsTable    table.Model
	// matrixTable    table.Model
	width, height int
}



@@ 40,7 40,7 @@ func (m tabKnightsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

		}
	}
	// m.playsTable, cmd = m.playsTable.Update(msg)
	// m.matrixTable, cmd = m.matrixTable.Update(msg)

	return m, cmd
}


@@ 48,18 48,23 @@ func (m tabKnightsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m tabKnightsModel) View() string {
	/*
		if m.height > 0 {
			m.playsTable.SetHeight(m.height - 12) // TODO: magic number
			m.matrixTable.SetHeight(m.height - 12) // TODO: magic number
		}
		return basicTableStyle.Render(m.playsTable.View())
		return basicTableStyle.Render(m.matrixTable.View())
	*/
	return "TODO"
}

func newTabKnights(plays []astro_knights.Play) tea.Model {
	// stats, err := astro_knights.GetKnightUseStats(plays)
	// if err != nil {
	// 	panic(err)
	// }

	return tabKnightsModel{}
	/*
		return tabKnightsModel{
			playsTable: createPlaysTable(plays),
			matrixTable: createPlaysTable(plays),
		}
	*/
}

A collection.go => collection.go +86 -0
@@ 0,0 1,86 @@
package astro_knights

import "fmt"

type UserCollection struct {
	BggUsername string   `json:"bgg_username"`
	Editions    []string `json:"editions"`

	editionIds  []EditionID
	knights     []KnightID
	bosses      []BossID
	homeworlds  []HomeworldID
	teamAttacks []TeamAttackID

	Plays []Play `json:"-"`
}

func (c *UserCollection) populateIDs() error {
	if c.editionIds != nil {
		return nil
	}

	c.editionIds = make([]EditionID, len(c.Editions))

	for i, ed := range c.Editions {
		if eid, ok := EditionNameToID(ed); ok {
			c.editionIds[i] = eid
		} else {
			return fmt.Errorf("could not determine ID of edition named '%s'", ed)
		}
	}

	c.knights = make([]KnightID, 0)
	for _, eid := range c.editionIds {
		c.knights = append(c.knights, Editions[eid].Knights...)
	}

	c.bosses = make([]BossID, 0)
	for _, eid := range c.editionIds {
		c.bosses = append(c.bosses, Editions[eid].Bosses...)
	}

	c.homeworlds = make([]HomeworldID, 0)
	for _, eid := range c.editionIds {
		c.homeworlds = append(c.homeworlds, Editions[eid].Homeworlds...)
	}

	c.teamAttacks = make([]TeamAttackID, 0)
	for _, eid := range c.editionIds {
		c.teamAttacks = append(c.teamAttacks, Editions[eid].TeamAttacks...)
	}

	return nil
}

func (c *UserCollection) Knights() ([]KnightID, error) {
	if err := c.populateIDs(); c.knights == nil && err != nil {
		return nil, err
	}

	return c.knights, nil
}

func (c *UserCollection) Bosses() ([]BossID, error) {
	if err := c.populateIDs(); c.bosses == nil && err != nil {
		return nil, err
	}

	return c.bosses, nil
}

func (c *UserCollection) Homeworlds() ([]HomeworldID, error) {
	if err := c.populateIDs(); c.homeworlds == nil && err != nil {
		return nil, err
	}

	return c.homeworlds, nil
}

func (c *UserCollection) TeamAttacks() ([]TeamAttackID, error) {
	if err := c.populateIDs(); c.teamAttacks == nil && err != nil {
		return nil, err
	}

	return c.teamAttacks, nil
}

M data.go => data.go +137 -78
@@ 1,64 1,33 @@
package astro_knights

type EditionID int

const (
	CoreGameID             EditionID = 352179
	OrionSystemExpansionID EditionID = 382126
	CoreGamePromoPack1ID   EditionID = 406201

	/*
		EternityGameID           EditionID = 386538
		EternityFlySavageSkiesID EditionID = 406204
		EternityMysterySolarusID EditionID = 406203
		EternityPromoPack1ID     EditionID = 406202
	*/
)

var EditionsIDs = [...]EditionID{
	CoreGameID,
	OrionSystemExpansionID,
	CoreGamePromoPack1ID,
	/*
		EternityGameID,
		EternityFlySavageSkiesID,
		EternityMysterySolarusID,
		EternityPromoPack1ID,
	*/
}

type HomeworldID string

const (
	Nisarin     HomeworldID = "Nisarin"
	Ylong       HomeworldID = "Ylong"
	Qaris       HomeworldID = "Qaris"
	NewAtlantis HomeworldID = "New Atlantis"

	OrionIII HomeworldID = "Orion III"

	Silarix HomeworldID = "Silarix"

	// Centuron HomeworldID = "Centuron "
)

var Homeworlds = [...]HomeworldID{Nisarin, Ylong, Qaris, NewAtlantis, OrionIII, Silarix}
import "strings"

type KnightID string

const (
	ZAK       KnightID = "Z.A.K"
	ZAK       KnightID = "Z.A.K."
	Christina KnightID = "Christina"
	Silas     KnightID = "Silas"
	Gavril    KnightID = "Gavril"
	Namas     KnightID = "Namas"
	Nasma     KnightID = "Nasma"
	Toli      KnightID = "Toli"

	Deleth  KnightID = "Deleth"
	Alexios KnightID = "Alexios"

	Caleb        KnightID = "Caleb"
	Reshi        KnightID = "Reshi"
	ZAK_Eternity KnightID = "Z.A.K. (Eternity)"
	Tsana        KnightID = "Tsana"

	Naoko    KnightID = "Naoko"
	Sunshine KnightID = "Sunshine"

	CaptainCadiz KnightID = "Captain Cadiz"
	Scuttlebutt  KnightID = "Scuttlebutt"
)

var Knights = [...]KnightID{ZAK, Christina, Silas, Gavril, Namas, Toli, Deleth, Alexios}
var Knights = [...]KnightID{ZAK, Christina, Silas, Gavril, Nasma, Toli, Deleth, Alexios}

type BossID string



@@ 69,6 38,12 @@ const (
	Continnua     BossID = "Continnua"

	FissionParasite BossID = "Fission Parasite"

	DirathianBehemoth BossID = "Dirathian Behemoth"

	ShadeSculptor BossID = "Shade Sculptor"

	TheBlackHoleGalleon BossID = "The Black Hole Galleon"
)

var Bosses = [...]BossID{Furion, ArchitectO815, Lunaris, Continnua, FissionParasite}


@@ 83,59 58,143 @@ const (

var BossDifficulties = [...]BossDifficulty{BossNormal, BossExpert, BossNightmare}

type HomeworldID string

const (
	Nisarin     HomeworldID = "Nisarin"
	Ylong       HomeworldID = "Ylong"
	Qaris       HomeworldID = "Qaris"
	NewAtlantis HomeworldID = "New Atlantis"

	OrionIII HomeworldID = "Orion III"

	Silarix HomeworldID = "Silarix"

	Dirath HomeworldID = "Dirath"

	Eos HomeworldID = "Eos"

	NassaiIV HomeworldID = "Nassai IV"

	Centuron HomeworldID = "Centuron"
)

var Homeworlds = [...]HomeworldID{Nisarin, Ylong, Qaris, NewAtlantis, OrionIII, Silarix}

type TeamAttackID string

const (
	NoKnightLeftBehind TeamAttackID = "No Knight Left Behind!"

	StarburstFinale TeamAttackID = "Starburst Finale!"

	HitThemWhereItHurts TeamAttackID = "Hit Them Where It Hurts!"
)

type EditionID int

const (
	CoreGameID             EditionID = 352179
	OrionSystemExpansionID EditionID = 382126
	CoreGamePromoPack1ID   EditionID = 406201

	EternityGameID           EditionID = 386538
	EternitySavageSkiesID    EditionID = 406204
	EternityMysterySolarusID EditionID = 406203
	EternityPromoPack1ID     EditionID = 406202
)

var EditionsIDs = [...]EditionID{
	CoreGameID,
	OrionSystemExpansionID,
	CoreGamePromoPack1ID,
	EternityGameID,
	EternitySavageSkiesID,
	EternityMysterySolarusID,
	EternityPromoPack1ID,
}

var editionNameToID = map[string]EditionID{
	"astro knights":             CoreGameID,
	"core":                      CoreGameID,
	"the orion system":          OrionSystemExpansionID,
	"orion system":              OrionSystemExpansionID,
	"astro knights: promo pack": CoreGamePromoPack1ID,
	"core promo pack":           CoreGamePromoPack1ID,
	"astro knights: eternity":   EternityGameID,
	"eternity":                  EternityGameID,
	"savage skies":              EternitySavageSkiesID,
	"fly the savage skies":      EternitySavageSkiesID,
	"mystery of solarus":        EternityMysterySolarusID,
	"eternity promo pack":       EternityPromoPack1ID,
}

func EditionNameToID(name string) (EditionID, bool) {
	normalized := strings.ToLower(strings.TrimSpace(name))
	if id, ok := editionNameToID[normalized]; ok {
		return id, true
	}
	return -1, false
}

type Edition struct {
	ID         EditionID
	Homeworlds []HomeworldID
	Knights    []KnightID
	Bosses     []BossID
	ID          EditionID
	Knights     []KnightID
	Bosses      []BossID
	Homeworlds  []HomeworldID
	TeamAttacks []TeamAttackID
}

var Editions = map[EditionID]Edition{
	// Core
	CoreGameID: Edition{
		ID:         CoreGameID,
		Homeworlds: []HomeworldID{Nisarin, Ylong, Qaris, NewAtlantis},
		Knights:    []KnightID{ZAK, Christina, Silas, Gavril, Namas, Toli},
		Knights:    []KnightID{ZAK, Christina, Silas, Gavril, Nasma, Toli},
		Bosses:     []BossID{Furion, ArchitectO815, Lunaris, Continnua},
		Homeworlds: []HomeworldID{Nisarin, Ylong, Qaris, NewAtlantis},
	},
	OrionSystemExpansionID: Edition{
		ID:         OrionSystemExpansionID,
		Homeworlds: []HomeworldID{OrionIII},
		Knights:    []KnightID{Deleth, Alexios},
		Bosses:     []BossID{FissionParasite},
		Homeworlds: []HomeworldID{OrionIII},
	},
	CoreGamePromoPack1ID: Edition{
		ID:         CoreGamePromoPack1ID,
		Homeworlds: []HomeworldID{Silarix},
	},
	// Eternity
	/*
		EternityGameID: Edition{
			ID:         EternityGameID,
			Homeworlds: []HomeworldID{},
			Knights:    []KnightID{},
			Bosses:     []BossID{},
		},
		EternityFlySavageSkiesID: Edition{
			ID:         EternityFlySavageSkiesID,
			Homeworlds: []HomeworldID{},
			Knights:    []KnightID{},
			Bosses:     []BossID{},
		},
		EternityMysterySolarusID: Edition{
			ID:         EternityMysterySolarusID,
			Homeworlds: []HomeworldID{},
			Knights:    []KnightID{},
			Bosses:     []BossID{},
		},
		EternityPromoPack1ID: Edition{
			ID:         EternityPromoPack1ID,
			Homeworlds: []HomeworldID{Centuron},
		},
	*/
	EternityGameID: Edition{
		ID:          EternityGameID,
		Knights:     []KnightID{Caleb, Reshi, ZAK_Eternity, Tsana},
		Bosses:      []BossID{DirathianBehemoth},
		Homeworlds:  []HomeworldID{Dirath},
		TeamAttacks: []TeamAttackID{},
	},
	EternitySavageSkiesID: Edition{
		ID:          EternitySavageSkiesID,
		Knights:     []KnightID{Naoko, Sunshine},
		Bosses:      []BossID{ShadeSculptor},
		Homeworlds:  []HomeworldID{Eos},
		TeamAttacks: []TeamAttackID{StarburstFinale},
	},
	EternityMysterySolarusID: Edition{
		ID:          EternityMysterySolarusID,
		Knights:     []KnightID{CaptainCadiz, Scuttlebutt},
		Bosses:      []BossID{TheBlackHoleGalleon},
		Homeworlds:  []HomeworldID{NassaiIV},
		TeamAttacks: []TeamAttackID{NoKnightLeftBehind},
	},
	EternityPromoPack1ID: Edition{
		ID:          EternityPromoPack1ID,
		Homeworlds:  []HomeworldID{Centuron},
		TeamAttacks: []TeamAttackID{HitThemWhereItHurts},
	},
}

/*
var EditionsCompatability = [][]EditionID{
	{CoreGameID, OrionSystemExpansionID, CoreGamePromoPack1ID},
	// {EternityGameID, EternityFlySavageSkiesID, EternityMysterySolarusID, EternityPromoPack1ID},
	{EternityGameID, EternitySavageSkiesID, EternityMysterySolarusID, EternityPromoPack1ID},
}
*/

M stats.go => stats.go +94 -2
@@ 93,6 93,7 @@ func GetPlaysByUsername(username string) ([]Play, error) {
}

func GetBossUseStats(plays []Play) (map[BossID]map[BossDifficulty]int, error) {
	// TODO: boss win count
	stats := make(map[BossID]map[BossDifficulty]int)

	for _, b := range Bosses {


@@ 123,11 124,102 @@ func GetHomeworldUseStats(plays []Play) (map[HomeworldID]int, error) {
	return stats, nil
}

func GetKnightUseStats(plays []Play) { //(map[KnightID][]
// type knightCombinationStats [][]KnightID
// type KnightUseStats map[KnightID]knightCombinationStats
/*
type knightCombinationStats struct {
	Knights []KnightID
	Count   int
}
*/

type KnightUseStats struct {
	// Combinations []knightCombinationStats
	// Matrix       map[KnightID][]knightCombinationStats
	Combinations map[[4]KnightID]int
	Matrix       map[KnightID]map[KnightID]int
}

func permsExample() {
	nextPerm := func(p []int) {
		for i := len(p) - 1; i >= 0; i-- {
			if i == 0 || p[i] < len(p)-i-1 {
				p[i]++
				return
			}
			p[i] = 0
		}
	}

	orig := []int{11, 22, 33}
	for p := make([]int, len(orig)); p[0] < len(p); nextPerm(p) {
		perm := append([]int{}, orig...)
		for i, v := range p {
			perm[i], perm[i+v] = perm[i+v], perm[i]
		}
		fmt.Println(perm)
	}
}

func GetKnightUseStats(collection UserCollection) (KnightUseStats, error) {
	stats := KnightUseStats{
		// Combinations: make([]knightCombinationStats, 0),
		// Matrix:       make(map[KnightID][]knightCombinationStats),
		Combinations: make(map[[4]KnightID]int),
		Matrix:       make(map[KnightID]map[KnightID]int),
	}

	// initialize the matrix
	knights, err := collection.Knights()
	if err != nil {
		return stats, err
	}
	for _, k1 := range knights {
		stats.Matrix[k1] = make(map[KnightID]int)
		// stats.Matrix[k1] = make([]knightCombinationStats, 0, len(Knights))

		for _, k2 := range knights {
			// stats.Matrix[k1] = append(stats.Matrix[k1], knightCombinationStats{[]KnightID{k2}, 0})
			stats.Matrix[k1][k2] = 0
		}
	}

	// initialize the combinations
	/*
		combos := make([][4]KnightID, 0)
		for _, k1 := range Knights {
			// TODO
			for _, k2 := range Knights {
			}
		}
		for _, c := range combos {
			stats.Combinations[c] = 0
		}
	*/

	// populate matrix with plays
	for _, p := range collection.Plays {
		// fmt.Println(p.Knights)
		for _, k1 := range p.Knights {
			for _, k2 := range p.Knights {
				if k2 != k1 {
					/*
						fmt.Println("  ", k1, k2)
						if _, ok := stats.Matrix[k1]; !ok {
							fmt.Println("did not find", k1)
							fmt.Printf("%+v\n", stats.Matrix)
						}
					*/
					stats.Matrix[k1][k2] += 1
				}
			}
		}
	}

	return stats, nil
}

/*
- knight combinations
- boss win counts
- homeworld, boss+str, knights combinations
*/