wip: generate report
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
mr "github.com/iliadenisov/galaxy/internal/model/report"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
)
|
||||
|
||||
func (c *Cache) Report(t uint, battleReports []*mr.BattleReport, bombingReports []*mr.Bombing) iter.Seq[*mr.Report] {
|
||||
report := c.InitReport(t)
|
||||
return func(yield func(*mr.Report) bool) {
|
||||
for i := range c.g.Race {
|
||||
c.ReportRace(i, report, battleReports)
|
||||
if !yield(report) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) InitReport(t uint) *mr.Report {
|
||||
report := &mr.Report{
|
||||
Turn: t,
|
||||
Width: c.g.Map.Width,
|
||||
Height: c.g.Map.Height,
|
||||
PlanetCount: uint32(len(c.g.Map.Planet)),
|
||||
Player: make([]mr.Player, len(c.g.Race)),
|
||||
LocalScience: make([]mr.Science, 0, 10),
|
||||
OtherScience: make([]mr.OtherScience, 0, 10),
|
||||
LocalShipClass: make([]mr.ShipClass, 0, 20),
|
||||
OtherShipClass: make([]mr.OthersShipClass, 0, 50),
|
||||
Battle: make([]uuid.UUID, 0, 10),
|
||||
Bombing: make([]*mr.Bombing, 0, 10),
|
||||
IncomingGroup: make([]mr.IncomingGroup, 0, 10),
|
||||
PlanetGroupsCache: make(map[uint][]int),
|
||||
}
|
||||
|
||||
sumVote, sumPop, sumInd := make(map[int]float64), make(map[int]float64), make(map[int]float64)
|
||||
planets := make(map[int]uint16)
|
||||
for i := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[i]
|
||||
if p.Owner == uuid.Nil {
|
||||
continue
|
||||
}
|
||||
ri := c.RaceIndex(p.Owner)
|
||||
sumPop[ri] += p.Population.F()
|
||||
sumInd[ri] += p.Industry.F()
|
||||
planets[ri] = planets[ri] + 1
|
||||
}
|
||||
|
||||
for ri := range c.g.Race {
|
||||
r := &c.g.Race[ri]
|
||||
rr := &report.Player[ri]
|
||||
|
||||
rr.ID = r.ID
|
||||
rr.Name = r.Name
|
||||
rr.Drive = mr.F(r.TechLevel(game.TechDrive))
|
||||
rr.Weapons = mr.F(r.TechLevel(game.TechWeapons))
|
||||
rr.Shields = mr.F(r.TechLevel(game.TechShields))
|
||||
rr.Cargo = mr.F(r.TechLevel(game.TechCargo))
|
||||
rr.Planets = planets[ri]
|
||||
rr.Population = mr.F(sumPop[ri])
|
||||
rr.Industry = mr.F(sumInd[ri])
|
||||
|
||||
// give voices by race index
|
||||
if vi := slices.IndexFunc(c.g.Race, func(v game.Race) bool { return r.VoteFor == v.ID }); vi < 0 {
|
||||
panic(fmt.Sprintf("voting for unknown race, id=%v", r.VoteFor))
|
||||
} else {
|
||||
sumVote[vi] += r.Votes
|
||||
dest := &report.Player[vi]
|
||||
dest.Votes = mr.F(sumVote[vi])
|
||||
}
|
||||
|
||||
// collect all orbiting ship groups by planet
|
||||
for sgi := range c.g.ShipGroups {
|
||||
sg := &c.g.ShipGroups[sgi]
|
||||
if sg.State() == game.StateInSpace {
|
||||
continue
|
||||
}
|
||||
report.PlanetGroupsCache[sg.Destination] = append(report.PlanetGroupsCache[sg.Destination], sgi)
|
||||
}
|
||||
}
|
||||
slices.SortFunc(report.Player, func(a, b mr.Player) int { return cmp.Compare(a.Name, b.Name) })
|
||||
|
||||
return report
|
||||
}
|
||||
|
||||
func (c *Cache) ReportRace(ri int, rep *mr.Report, br []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
rep.Race = r.Name
|
||||
rep.RaceID = r.ID
|
||||
|
||||
// votes based on population
|
||||
// TODO: check votes was calculated
|
||||
rep.Votes = mr.F(r.Votes)
|
||||
|
||||
// relations
|
||||
for i := range r.Relations {
|
||||
rii := slices.IndexFunc(rep.Player, func(v mr.Player) bool { return v.ID == r.Relations[i].RaceID })
|
||||
if rii < 0 {
|
||||
panic(fmt.Sprintf("relation race not found, id=%v", r.Relations[i].RaceID))
|
||||
}
|
||||
rep.Player[rii].Relation = r.Relations[i].Relation.String()
|
||||
}
|
||||
// self-relation is undefined
|
||||
|
||||
if i := slices.IndexFunc(rep.Player, func(v mr.Player) bool { return v.ID == r.ID }); i < 0 {
|
||||
panic(fmt.Sprintf("race not found in report, id=%v", r.ID))
|
||||
} else {
|
||||
rep.Player[i].Relation = "-"
|
||||
}
|
||||
|
||||
// sciences
|
||||
c.ReportLocalScience(ri, rep)
|
||||
c.ReportOtherScience(ri, rep)
|
||||
|
||||
// ship classes
|
||||
c.ReportLocalShipClass(ri, rep)
|
||||
c.ReportOtherShipClass(ri, rep, br)
|
||||
}
|
||||
|
||||
func (c *Cache) ReportLocalScience(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.LocalScience)
|
||||
|
||||
for i := range r.Sciences {
|
||||
sliceIndexValidate(&rep.LocalScience, i)
|
||||
rep.LocalScience[i].Name = r.Sciences[i].Name
|
||||
rep.LocalScience[i].Drive = mr.F(r.Sciences[i].Drive)
|
||||
rep.LocalScience[i].Weapons = mr.F(r.Sciences[i].Weapons)
|
||||
rep.LocalScience[i].Shields = mr.F(r.Sciences[i].Shields)
|
||||
rep.LocalScience[i].Cargo = mr.F(r.Sciences[i].Cargo)
|
||||
}
|
||||
|
||||
slices.SortFunc(rep.LocalScience, func(a, b mr.Science) int { return cmp.Compare(a.Name, b.Name) })
|
||||
}
|
||||
|
||||
func (c *Cache) ReportOtherScience(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.OtherScience)
|
||||
|
||||
i := 0
|
||||
for sg := range c.listShipGroups(ri) {
|
||||
if sg.State() != game.StateInOrbit {
|
||||
continue
|
||||
}
|
||||
p := c.MustPlanet(sg.Destination)
|
||||
if p.Owner == uuid.Nil || p.Owner == r.ID || p.Production.Type != game.ResearchScience {
|
||||
continue
|
||||
}
|
||||
ownerIdx := c.RaceIndex(p.Owner)
|
||||
owner := &c.g.Race[ownerIdx]
|
||||
sc := c.mustScience(ownerIdx, *p.Production.SubjectID)
|
||||
|
||||
sliceIndexValidate(&rep.OtherScience, i)
|
||||
rep.OtherScience[i].Name = owner.Name
|
||||
rep.OtherScience[i].Drive = mr.F(sc.Drive)
|
||||
rep.OtherScience[i].Weapons = mr.F(sc.Weapons)
|
||||
rep.OtherScience[i].Shields = mr.F(sc.Shields)
|
||||
rep.OtherScience[i].Cargo = mr.F(sc.Cargo)
|
||||
i++
|
||||
}
|
||||
|
||||
slices.SortFunc(rep.OtherScience, func(a, b mr.OtherScience) int {
|
||||
return cmp.Or(cmp.Compare(a.Race, b.Race), cmp.Compare(a.Name, b.Name))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) ReportLocalShipClass(ri int, report *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
|
||||
clear(report.LocalShipClass)
|
||||
|
||||
i := 0
|
||||
for st := range c.ListShipTypes(ri) {
|
||||
sliceIndexValidate(&report.LocalShipClass, i)
|
||||
report.LocalShipClass[i].Name = st.Name
|
||||
report.LocalShipClass[i].Drive = mr.F(st.Drive)
|
||||
report.LocalShipClass[i].Armament = st.Armament
|
||||
report.LocalShipClass[i].Weapons = mr.F(st.Weapons)
|
||||
report.LocalShipClass[i].Shields = mr.F(st.Shields)
|
||||
report.LocalShipClass[i].Cargo = mr.F(st.Cargo)
|
||||
report.LocalShipClass[i].Mass = mr.F(st.EmptyMass())
|
||||
i++
|
||||
}
|
||||
|
||||
slices.SortFunc(report.LocalShipClass, func(a, b mr.ShipClass) int { return cmp.Compare(a.Name, b.Name) })
|
||||
}
|
||||
|
||||
func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report, br []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.OtherShipClass)
|
||||
|
||||
i := 0
|
||||
|
||||
used := make(map[uuid.UUID]map[string]bool)
|
||||
usedFn := func(ownerID uuid.UUID, className string) bool {
|
||||
if _, ok := used[ownerID]; ok {
|
||||
if _, ok := used[ownerID][className]; ok {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
used[ownerID] = make(map[string]bool)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// add visible ship classes from battles
|
||||
for bi := range br {
|
||||
for si := range br[bi].Ships {
|
||||
g := br[bi].Ships[si]
|
||||
if g.OwnerID == r.ID || usedFn(g.OwnerID, g.ClassName) {
|
||||
continue
|
||||
}
|
||||
used[g.OwnerID][g.ClassName] = true
|
||||
|
||||
sliceIndexValidate(&rep.OtherShipClass, i)
|
||||
rep.OtherShipClass[i].Race = c.g.Race[c.RaceIndex(g.OwnerID)].Name
|
||||
rep.OtherShipClass[i].Name = g.ClassName
|
||||
rep.OtherShipClass[i].Drive = g.DriveTech
|
||||
rep.OtherShipClass[i].Armament = g.ClassArmament
|
||||
rep.OtherShipClass[i].Weapons = g.WeaponsTech
|
||||
rep.OtherShipClass[i].Shields = g.ShieldsTech
|
||||
rep.OtherShipClass[i].Cargo = g.CargoTech
|
||||
rep.OtherShipClass[i].Mass = g.ClassMass
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// add visible ship classes from observable planets
|
||||
for pn := range rep.PlanetGroupsCache {
|
||||
if slices.IndexFunc(rep.PlanetGroupsCache[pn], func(sgi int) bool { return c.ShipGroup(sgi).OwnerID == r.ID }) >= 0 {
|
||||
for _, sgi := range rep.PlanetGroupsCache[pn] {
|
||||
sg := c.ShipGroup(sgi)
|
||||
if sg.OwnerID == r.ID {
|
||||
continue
|
||||
}
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
|
||||
sliceIndexValidate(&rep.OtherShipClass, i)
|
||||
rep.OtherShipClass[i].Race = c.g.Race[c.RaceIndex(sg.OwnerID)].Name
|
||||
rep.OtherShipClass[i].Name = st.Name
|
||||
rep.OtherShipClass[i].Drive = mr.F(st.Drive)
|
||||
rep.OtherShipClass[i].Armament = st.Armament
|
||||
rep.OtherShipClass[i].Weapons = mr.F(st.Weapons)
|
||||
rep.OtherShipClass[i].Shields = mr.F(st.Shields)
|
||||
rep.OtherShipClass[i].Cargo = mr.F(st.Cargo)
|
||||
rep.OtherShipClass[i].Mass = mr.F(st.EmptyMass())
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(rep.OtherShipClass, func(a, b mr.OthersShipClass) int {
|
||||
return cmp.Or(cmp.Compare(a.Race, b.Race), cmp.Compare(a.Name, b.Name))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) ReportBattle(ri int, rep *mr.Report, br []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.Battle)
|
||||
|
||||
i := 0
|
||||
for bi := range br {
|
||||
visible := false
|
||||
for k := range br[bi].Races {
|
||||
visible = visible || br[bi].Races[k] == r.ID
|
||||
}
|
||||
if !visible {
|
||||
continue
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.Battle, i)
|
||||
rep.Battle[i] = br[bi].ID
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ReportBombing(ri int, rep *mr.Report, bombing []*mr.Bombing, battle []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.Bombing)
|
||||
|
||||
i := 0
|
||||
for bi := range bombing {
|
||||
pn := bombing[bi].Number
|
||||
visible := bombing[bi].PlanetOwnedID == r.ID // planet may be bombed and wiped
|
||||
for _, sgi := range rep.PlanetGroupsCache[pn] {
|
||||
sg := c.ShipGroup(sgi)
|
||||
visible = visible || (sg.OwnerID == r.ID && sg.Destination == pn)
|
||||
}
|
||||
if !visible {
|
||||
continue
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.Bombing, i)
|
||||
rep.Bombing[i] = bombing[bi]
|
||||
i++
|
||||
}
|
||||
|
||||
slices.SortFunc(rep.Bombing, func(a, b *mr.Bombing) int {
|
||||
return cmp.Or(cmp.Compare(a.Number, b.Number), boolCompare(a.Wiped, b.Wiped))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) ReportIncomingGroup(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.IncomingGroup)
|
||||
|
||||
i := 0
|
||||
for sgi := range c.ShipGroupsIndex() {
|
||||
sg := c.ShipGroup(sgi)
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
if sg.OwnerID == r.ID || sg.State() != game.StateInSpace {
|
||||
continue
|
||||
}
|
||||
p1 := c.MustPlanet(sg.StateInSpace.Origin)
|
||||
p2 := c.MustPlanet(sg.Destination)
|
||||
if p2.Owner != r.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
distance := util.ShortDistance(c.g.Map.Width, c.g.Map.Height, p1.X.F(), p1.Y.F(), p2.X.F(), p2.Y.F())
|
||||
var speed, mass float64
|
||||
if sg.FleetID != nil {
|
||||
speed, mass = c.FleetSpeedAndMass(c.MustFleetIndex(*sg.FleetID))
|
||||
} else {
|
||||
speed, mass = sg.Speed(st), sg.FullMass(st)
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.IncomingGroup, i)
|
||||
rep.IncomingGroup[i].Origin = sg.StateInSpace.Origin
|
||||
rep.IncomingGroup[i].Destination = sg.Destination
|
||||
rep.IncomingGroup[i].Distance = mr.F(distance)
|
||||
rep.IncomingGroup[i].Speed = mr.F(speed)
|
||||
rep.IncomingGroup[i].Mass = mr.F(mass)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ReportLocalPlanet(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.LocalPlanet)
|
||||
|
||||
i := 0
|
||||
for pi := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[pi]
|
||||
if p.Owner != r.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.LocalPlanet, i)
|
||||
rep.LocalPlanet[i].Number = p.Number
|
||||
rep.LocalPlanet[i].X = mr.F(p.X.F())
|
||||
rep.LocalPlanet[i].Y = mr.F(p.Y.F())
|
||||
rep.LocalPlanet[i].Size = mr.F(p.Size.F())
|
||||
rep.LocalPlanet[i].Name = p.Name
|
||||
rep.LocalPlanet[i].Resources = mr.F(p.Resources.F())
|
||||
rep.LocalPlanet[i].Capital = mr.F(p.Capital.F())
|
||||
rep.LocalPlanet[i].Material = mr.F(p.Material.F())
|
||||
rep.LocalPlanet[i].Industry = mr.F(p.Industry.F())
|
||||
rep.LocalPlanet[i].Population = mr.F(p.Population.F())
|
||||
rep.LocalPlanet[i].Colonists = mr.F(p.Colonists.F())
|
||||
rep.LocalPlanet[i].Production = c.PlanetProductionDisplayName(p.Number)
|
||||
rep.LocalPlanet[i].FreeIndustry = mr.F(p.ProductionCapacity())
|
||||
for _, sgi := range rep.PlanetGroupsCache[p.Number] {
|
||||
sg := c.ShipGroup(sgi)
|
||||
if sg.StateUpgrade == nil {
|
||||
break
|
||||
}
|
||||
// between-turn report: ships upgrading on the planet decreases free indistrial potential
|
||||
rep.LocalPlanet[i].FreeIndustry -= mr.F(sg.StateUpgrade.Cost())
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ReportShipProduction(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.ShipProduction)
|
||||
|
||||
i := 0
|
||||
for pi := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[pi]
|
||||
if p.Owner != r.ID || p.Production.Type != game.ProductionShip {
|
||||
continue
|
||||
}
|
||||
st := c.MustShipType(ri, *p.Production.SubjectID)
|
||||
|
||||
sliceIndexValidate(&rep.ShipProduction, i)
|
||||
free := c.PlanetProductionCapacity(p.Number)
|
||||
rep.ShipProduction[pi].Planet = p.Number
|
||||
rep.ShipProduction[pi].Class = st.Name
|
||||
rep.ShipProduction[pi].Cost = mr.F(st.EmptyMass())
|
||||
rep.ShipProduction[pi].Free = mr.F(free)
|
||||
|
||||
// FIXME: take logic from [ProduceShip] and test at [controller_test.TestProduceShip]
|
||||
rep.ShipProduction[pi].Wasted = mr.F(free * *p.Production.Progress)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ReportRoute(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.Route)
|
||||
|
||||
i := 0
|
||||
for pi := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[pi]
|
||||
if p.Owner != r.ID || len(p.Route) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.Route, i)
|
||||
rep.Route[i].Planet = p.Number
|
||||
// rep.Route[i].Route = make(map[uint]string)
|
||||
for rt, dest := range p.Route {
|
||||
rep.Route[i].Route[dest] = rt.String()
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func sliceIndexValidate[S ~[]E, E any](s *S, i int) {
|
||||
if cap(*s) < i+1 {
|
||||
*s = slices.Grow(*s, 10)
|
||||
}
|
||||
if len(*s) < i+1 {
|
||||
*s = (*s)[:i+1]
|
||||
}
|
||||
}
|
||||
|
||||
func boolCompare(a, b bool) int {
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
if a == false {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
Reference in New Issue
Block a user