diff --git a/internal/controller/battle_transform.go b/internal/controller/battle_transform.go index a818e3b..477c646 100644 --- a/internal/controller/battle_transform.go +++ b/internal/controller/battle_transform.go @@ -2,7 +2,6 @@ package controller import ( "github.com/google/uuid" - "github.com/iliadenisov/galaxy/internal/model/game" "github.com/iliadenisov/galaxy/internal/model/report" ) @@ -23,22 +22,26 @@ func TransformBattle(c *Cache, b *Battle) *report.BattleReport { shipClass := c.ShipGroupShipClass(groupId) sg := c.ShipGroup(groupId) itemNumber := len(r.Ships) - r.Ships[itemNumber] = report.BattleReportGroup{ - OwnerID: sg.OwnerID, - Race: c.g.Race[c.RaceIndex(sg.OwnerID)].Name, - InBattle: inBattle, - Number: b.initialNumbers[groupId], - NumberLeft: sg.Number, - ClassName: shipClass.Name, - LoadType: sg.CargoString(), - LoadQuantity: report.F(sg.Load.F()), - DriveTech: report.F(sg.TechLevel(game.TechDrive).F()), - ClassArmament: shipClass.Armament, - WeaponsTech: report.F(sg.TechLevel(game.TechWeapons).F()), - ShieldsTech: report.F(sg.TechLevel(game.TechShields).F()), - CargoTech: report.F(sg.TechLevel(game.TechCargo).F()), - ClassMass: report.F(shipClass.EmptyMass()), + bg := &report.BattleReportGroup{ + // OwnerID: sg.OwnerID, + // ClassArmament: shipClass.Armament, + // ClassMass: report.F(shipClass.EmptyMass()), + Race: c.g.Race[c.RaceIndex(sg.OwnerID)].Name, + InBattle: inBattle, + Number: b.initialNumbers[groupId], + NumberLeft: sg.Number, + ClassName: shipClass.Name, + LoadType: sg.CargoString(), + LoadQuantity: report.F(sg.Load.F()), + // DriveTech: report.F(sg.TechLevel(game.TechDrive).F()), + // WeaponsTech: report.F(sg.TechLevel(game.TechWeapons).F()), + // ShieldsTech: report.F(sg.TechLevel(game.TechShields).F()), + // CargoTech: report.F(sg.TechLevel(game.TechCargo).F()), } + for t, v := range sg.Tech { + bg.Tech[t.String()] = report.F(v) + } + r.Ships[itemNumber] = *bg cacheShipClass[shipClass.ID] = itemNumber return itemNumber } diff --git a/internal/controller/fleet.go b/internal/controller/fleet.go index 0c2f55d..03bba02 100644 --- a/internal/controller/fleet.go +++ b/internal/controller/fleet.go @@ -31,9 +31,9 @@ func (c *Cache) FleetState(fleetID uuid.UUID) (game.ShipGroupState, *uint, *game panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different states", c.g.Race[ri].Name, c.g.Fleets[fi].Name)) } if planet, ok := sg.OnPlanet(); ok && onPlanet != nil && *onPlanet != planet { - for sg := range c.FleetGroups(ri, fi) { - fmt.Println("group", sg.Index, "fleet", sg.FleetID, c.g.Fleets[fi].Name, "state", sg.State(), "on", sg.Destination) - } + // for sg := range c.FleetGroups(ri, fi) { + // fmt.Println("group", sg.Index, "fleet", sg.FleetID, c.g.Fleets[fi].Name, "state", sg.State(), "on", sg.Destination) + // } panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q are on different planets: %d <> %d", c.g.Race[ri].Name, c.g.Fleets[fi].Name, *onPlanet, planet)) } if (is == nil && sg.StateInSpace != nil) || (is != nil && sg.StateInSpace == nil) { diff --git a/internal/controller/report.go b/internal/controller/report.go index 25e091e..25e2967 100644 --- a/internal/controller/report.go +++ b/internal/controller/report.go @@ -12,11 +12,11 @@ import ( "github.com/iliadenisov/galaxy/internal/util" ) -func (c *Cache) Report(t uint, battleReports []*mr.BattleReport, bombingReports []*mr.Bombing) iter.Seq[*mr.Report] { +func (c *Cache) Report(t uint, battles []*mr.BattleReport, bombings []*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) + c.ReportRace(i, report, battles, bombings) if !yield(report) { break } @@ -26,25 +26,26 @@ func (c *Cache) Report(t uint, battleReports []*mr.BattleReport, bombingReports 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), + 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), + OnPlanetGroupCache: make(map[uint][]int), + InSpaceGroupRangeCache: make(map[int]map[uint]float64), } 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] + for pi := range c.g.Map.Planet { + p := &c.g.Map.Planet[pi] if p.Owner == uuid.Nil { continue } @@ -76,22 +77,31 @@ func (c *Cache) InitReport(t uint) *mr.Report { 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) }) + for sgi := range c.g.ShipGroups { + sg := &c.g.ShipGroups[sgi] + if sg.State() == game.StateInSpace { + // pre-calculate distances from in_space ship groups to every planet + if _, ok := report.InSpaceGroupRangeCache[sgi]; !ok { + report.InSpaceGroupRangeCache[sgi] = make(map[uint]float64) + } + for pi := range c.g.Map.Planet { + p2 := &c.g.Map.Planet[pi] + distance := util.ShortDistance(c.g.Map.Width, c.g.Map.Height, sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), p2.X.F(), p2.Y.F()) + report.InSpaceGroupRangeCache[sgi][p2.Number] = distance + } + } else { + // collect all orbiting ship groups by planet + report.OnPlanetGroupCache[sg.Destination] = append(report.OnPlanetGroupCache[sg.Destination], sgi) + } + } + return report } -func (c *Cache) ReportRace(ri int, rep *mr.Report, br []*mr.BattleReport) { +func (c *Cache) ReportRace(ri int, rep *mr.Report, battles []*mr.BattleReport, bombings []*mr.Bombing) { c.validateRaceIndex(ri) r := &c.g.Race[ri] @@ -99,7 +109,7 @@ func (c *Cache) ReportRace(ri int, rep *mr.Report, br []*mr.BattleReport) { rep.RaceID = r.ID // votes based on population - // TODO: check votes was calculated + // TODO: check vote values was previously calculated rep.Votes = mr.F(r.Votes) // relations @@ -124,7 +134,46 @@ func (c *Cache) ReportRace(ri int, rep *mr.Report, br []*mr.BattleReport) { // ship classes c.ReportLocalShipClass(ri, rep) - c.ReportOtherShipClass(ri, rep, br) + c.ReportOtherShipClass(ri, rep) + + // battles + c.ReportBattle(ri, rep, battles) + + // bombings + c.ReportBombing(ri, rep, bombings) + + // incoming groups + c.ReportIncomingGroup(ri, rep) + + // player's planets + c.ReportLocalPlanet(ri, rep) + + // ships in production + c.ReportShipProduction(ri, rep) + + // cargo routes + c.ReportRoute(ri, rep) + + // others' planets + c.ReportOtherPlanet(ri, rep) + + // uninhabited planets + c.ReportUninhabitedPlanet(ri, rep) + + // unidentified planets + c.ReportUnidentifiedPlanet(ri, rep) + + // fleets + c.ReportLocalFleet(ri, rep) + + // player's groups + c.ReportLocalGroup(ri, rep) + + // others' groups + c.ReportOtherGroup(ri, rep) + + // unidentified groups + c.ReportUnidentifiedGroup(ri, rep) } func (c *Cache) ReportLocalScience(ri int, rep *mr.Report) { @@ -199,7 +248,8 @@ func (c *Cache) ReportLocalShipClass(ri int, report *mr.Report) { 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) { +// FIXME: add ships bombed/wiped planets without battles +func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] @@ -208,7 +258,10 @@ func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report, br []*mr.BattleRepo i := 0 used := make(map[uuid.UUID]map[string]bool) - usedFn := func(ownerID uuid.UUID, className string) bool { + skip := func(ownerID uuid.UUID, className string) bool { + if ownerID == r.ID { + return true + } if _, ok := used[ownerID]; ok { if _, ok := used[ownerID][className]; ok { return true @@ -216,40 +269,42 @@ func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report, br []*mr.BattleRepo } else { used[ownerID] = make(map[string]bool) } + used[ownerID][className] = true 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 + // for bi := range battle { + // for si := range battle[bi].Ships { + // g := battle[bi].Ships[si] + // if skip(g.OwnerID, g.ClassName) { + // continue + // } - 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++ - } - } + // 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] { + // add visible ships from owned and observed planets + for pn := range rep.OnPlanetGroupCache { + p := c.MustPlanet(pn) + if p.Owner == r.ID || + slices.IndexFunc(rep.OnPlanetGroupCache[pn], func(sgi int) bool { return c.ShipGroup(sgi).OwnerID == r.ID }) >= 0 { + for _, sgi := range rep.OnPlanetGroupCache[pn] { sg := c.ShipGroup(sgi) - if sg.OwnerID == r.ID { + st := c.ShipGroupShipClass(sgi) + if skip(sg.OwnerID, st.Name) { continue } - st := c.ShipGroupShipClass(sgi) sliceIndexValidate(&rep.OtherShipClass, i) rep.OtherShipClass[i].Race = c.g.Race[c.RaceIndex(sg.OwnerID)].Name @@ -292,7 +347,7 @@ func (c *Cache) ReportBattle(ri int, rep *mr.Report, br []*mr.BattleReport) { } } -func (c *Cache) ReportBombing(ri int, rep *mr.Report, bombing []*mr.Bombing, battle []*mr.BattleReport) { +func (c *Cache) ReportBombing(ri int, rep *mr.Report, bombing []*mr.Bombing) { c.validateRaceIndex(ri) r := &c.g.Race[ri] @@ -302,7 +357,7 @@ func (c *Cache) ReportBombing(ri int, rep *mr.Report, bombing []*mr.Bombing, bat 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] { + for _, sgi := range rep.OnPlanetGroupCache[pn] { sg := c.ShipGroup(sgi) visible = visible || (sg.OwnerID == r.ID && sg.Destination == pn) } @@ -371,27 +426,98 @@ func (c *Cache) ReportLocalPlanet(ri int, rep *mr.Report) { } 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()) + c.localPlanet(&rep.LocalPlanet[i], p, rep) + // rep.LocalPlanet[i].UnidentifiedPlanet.Number = p.Number + // rep.LocalPlanet[i].UnidentifiedPlanet.X = mr.F(p.X.F()) + // rep.LocalPlanet[i].UnidentifiedPlanet.Y = mr.F(p.Y.F()) + // rep.LocalPlanet[i].UninhabitedPlanet.Size = mr.F(p.Size.F()) + // rep.LocalPlanet[i].UninhabitedPlanet.Name = p.Name + // rep.LocalPlanet[i].UninhabitedPlanet.Resources = mr.F(p.Resources.F()) + // rep.LocalPlanet[i].UninhabitedPlanet.Capital = mr.F(p.Capital.F()) + // rep.LocalPlanet[i].UninhabitedPlanet.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) ReportOtherPlanet(ri int, rep *mr.Report) { + c.validateRaceIndex(ri) + r := &c.g.Race[ri] + + clear(rep.OtherPlanet) + + i := 0 + for sg := range c.listShipGroups(ri) { + if sg.State() != game.StateInOrbit { + continue } + p := c.MustPlanet(sg.Destination) + if p.Owner == r.ID || p.Owner == uuid.Nil { + continue + } + + sliceIndexValidate(&rep.OtherPlanet, i) + c.localPlanet(&rep.OtherPlanet[i].LocalPlanet, p, rep) + rep.OtherPlanet[i].Owner = c.g.Race[c.RaceIndex(p.Owner)].Name + i++ + } +} + +func (c *Cache) ReportUninhabitedPlanet(ri int, rep *mr.Report) { + c.validateRaceIndex(ri) + + clear(rep.UninhabitedPlanet) + + i := 0 + for sg := range c.listShipGroups(ri) { + if sg.State() != game.StateInOrbit { + continue + } + p := c.MustPlanet(sg.Destination) + if p.Owner != uuid.Nil { + continue + } + + sliceIndexValidate(&rep.UninhabitedPlanet, i) + uninhabitedPlanet(&rep.UninhabitedPlanet[i], p) + i++ + } +} + +func (c *Cache) ReportUnidentifiedPlanet(ri int, rep *mr.Report) { + c.validateRaceIndex(ri) + r := &c.g.Race[ri] + + clear(rep.UnidentifiedPlanet) + + i := 0 + for pi := range c.g.Map.Planet { + p := &c.g.Map.Planet[pi] + + // skip player's owned planets + if p.Owner == r.ID { + continue + } + + // skip planets where player's group are orbiting + if slices.IndexFunc(rep.OnPlanetGroupCache[p.Number], func(sgi int) bool { return c.ShipGroup(sgi).OwnerID == r.ID }) >= 0 { + continue + } + + sliceIndexValidate(&rep.UnidentifiedPlanet, i) + unidentifiedPlanet(&rep.UnidentifiedPlanet[i], p) i++ } } @@ -446,6 +572,199 @@ func (c *Cache) ReportRoute(ri int, rep *mr.Report) { } } +func (c *Cache) ReportLocalFleet(ri int, rep *mr.Report) { + c.validateRaceIndex(ri) + + clear(rep.LocalFleet) + + i := 0 + for fl := range c.listFleets(ri) { + fi := c.MustFleetIndex(fl.ID) + gid := slices.Collect(c.fleetGroupIds(ri, fi)) + if len(gid) == 0 { + continue + } + + speed, _ := c.FleetSpeedAndMass(fi) + state, _, inSpace := c.FleetState(fl.ID) + + sliceIndexValidate(&rep.LocalFleet, i) + rep.LocalFleet[i].Name = fl.Name + rep.LocalFleet[i].Groups = uint(len(gid)) + rep.LocalFleet[i].Speed = mr.F(speed) + rep.LocalFleet[i].State = state.String() + rep.LocalFleet[i].Destination = c.ShipGroup(gid[0]).Destination // FIXME: get fleet destination with some nicer way + if inSpace != nil { + rep.LocalFleet[i].Origin = &inSpace.Origin + p2 := c.MustPlanet(rep.LocalFleet[i].Destination) + rangeToDestination := mr.F(util.ShortDistance(c.g.Map.Width, c.g.Map.Height, inSpace.X.F(), inSpace.Y.F(), p2.X.F(), p2.Y.F())) + rep.LocalFleet[i].Range = &rangeToDestination + } + i++ + } +} + +func (c *Cache) ReportLocalGroup(ri int, rep *mr.Report) { + c.validateRaceIndex(ri) + + clear(rep.LocalGroup) + + i := 0 + for sg := range c.listShipGroups(ri) { + sliceIndexValidate(&rep.LocalGroup, i) + st := c.MustShipType(ri, sg.TypeID) + c.otherGroup(&rep.LocalGroup[i].OtherGroup, sg, st) + rep.LocalGroup[i].Index = sg.Index + rep.LocalGroup[i].State = sg.State().String() + if sg.FleetID != nil { + rep.LocalGroup[i].Fleet = &c.g.Fleets[c.MustFleetIndex(*sg.FleetID)].Name + } + // rep.LocalGroup[i].Number = sg.Number + // rep.LocalGroup[i].Class = st.Name + // // rep.LocalGroup[i].Tech = make(map[string]mr.Float) + // for t, v := range sg.Tech { + // rep.LocalGroup[i].Tech[t.String()] = mr.F(v) + // } + // rep.LocalGroup[i].Cargo = sg.CargoString() + // rep.LocalGroup[i].Load = mr.F(sg.Load.F()) + // rep.LocalGroup[i].Destination = sg.Destination + // if sg.State() == game.StateInSpace { + // rep.LocalGroup[i].Origin = &sg.StateInSpace.Origin + // p2 := c.MustPlanet(rep.LocalGroup[i].Destination) + // rangeToDestination := mr.F(util.ShortDistance(c.g.Map.Width, c.g.Map.Height, sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), p2.X.F(), p2.Y.F())) + // rep.LocalGroup[i].Range = &rangeToDestination + // } + // rep.LocalGroup[i].Speed = mr.F(sg.Speed(st)) + // rep.LocalGroup[i].Mass = mr.F(st.EmptyMass()) + i++ + } +} + +func (c *Cache) ReportOtherGroup(ri int, rep *mr.Report) { + c.validateRaceIndex(ri) + r := &c.g.Race[ri] + + clear(rep.OtherGroup) + + used := make(map[int]bool) + skip := func(sgi int) bool { + if c.ShipGroup(sgi).OwnerID == r.ID { + return true + } + if _, ok := used[sgi]; ok { + return true + } + used[sgi] = true + return false + } + + i := 0 + + // visible groups from owned and observed planets + for pn := range rep.OnPlanetGroupCache { + p := c.MustPlanet(pn) + if p.Owner == r.ID || + slices.IndexFunc(rep.OnPlanetGroupCache[pn], func(sgi int) bool { return c.ShipGroup(sgi).OwnerID == r.ID }) >= 0 { + for _, sgi := range rep.OnPlanetGroupCache[pn] { + sg := c.ShipGroup(sgi) + st := c.ShipGroupShipClass(sgi) + if skip(sgi) { + continue + } + + sliceIndexValidate(&rep.OtherGroup, i) + c.otherGroup(&rep.OtherGroup[i], sg, st) + i++ + } + } + } +} + +func (c *Cache) ReportUnidentifiedGroup(ri int, rep *mr.Report) { + c.validateRaceIndex(ri) + r := &c.g.Race[ri] + flightDistance := r.FlightDistance() + + clear(rep.UnidentifiedGroup) + + i := 0 + for sgi := range rep.InSpaceGroupRangeCache { + sg := c.ShipGroup(sgi) + if sg.OwnerID == rep.RaceID { + continue + } + if sg.StateInSpace == nil { + panic(fmt.Sprintf("pre-calculated distance group not in space: i=%d", sgi)) + } + for pi := range c.g.Map.Planet { + p := &c.g.Map.Planet[pi] + if p.Owner != r.ID { + continue + } + if v, ok := rep.InSpaceGroupRangeCache[sgi][p.Number]; !ok { + panic(fmt.Sprintf("distance cache not pre-calculated: i=%d p=#%d", sgi, p.Number)) + } else if v <= flightDistance { + sliceIndexValidate(&rep.UnidentifiedGroup, i) + rep.UnidentifiedGroup[i].X = mr.F(sg.StateInSpace.X.F()) + rep.UnidentifiedGroup[i].Y = mr.F(sg.StateInSpace.Y.F()) + i++ + } + } + } +} + +func (c *Cache) otherGroup(v *mr.OtherGroup, sg *game.ShipGroup, st *game.ShipType) { + v.Number = sg.Number + v.Class = st.Name + // rep.LocalGroup[i].Tech = make(map[string]mr.Float) + for t, val := range sg.Tech { + v.Tech[t.String()] = mr.F(val) + } + v.Cargo = sg.CargoString() + v.Load = mr.F(sg.Load.F()) + v.Destination = sg.Destination + if sg.State() == game.StateInSpace { + v.Origin = &sg.StateInSpace.Origin + p2 := c.MustPlanet(v.Destination) + rangeToDestination := mr.F(util.ShortDistance(c.g.Map.Width, c.g.Map.Height, sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), p2.X.F(), p2.Y.F())) + v.Range = &rangeToDestination + } + v.Speed = mr.F(sg.Speed(st)) + v.Mass = mr.F(st.EmptyMass()) +} + +func (c *Cache) localPlanet(v *mr.LocalPlanet, p *game.Planet, rep *mr.Report) { + uninhabitedPlanet(&v.UninhabitedPlanet, p) + v.Industry = mr.F(p.Industry.F()) + v.Population = mr.F(p.Population.F()) + v.Colonists = mr.F(p.Colonists.F()) + v.Production = c.PlanetProductionDisplayName(p.Number) + v.FreeIndustry = mr.F(p.ProductionCapacity()) + for _, sgi := range rep.OnPlanetGroupCache[p.Number] { + sg := c.ShipGroup(sgi) + if sg.StateUpgrade == nil { + break + } + // between-turn report: ships upgrading on the planet decreases free indistrial potential + v.FreeIndustry -= mr.F(sg.StateUpgrade.Cost()) + } +} + +func uninhabitedPlanet(v *mr.UninhabitedPlanet, p *game.Planet) { + unidentifiedPlanet(&v.UnidentifiedPlanet, p) + v.Size = mr.F(p.Size.F()) + v.Name = p.Name + v.Resources = mr.F(p.Resources.F()) + v.Capital = mr.F(p.Capital.F()) + v.Material = mr.F(p.Material.F()) +} + +func unidentifiedPlanet(v *mr.UnidentifiedPlanet, p *game.Planet) { + v.Number = p.Number + v.X = mr.F(p.X.F()) + v.Y = mr.F(p.Y.F()) +} + func sliceIndexValidate[S ~[]E, E any](s *S, i int) { if cap(*s) < i+1 { *s = slices.Grow(*s, 10) diff --git a/internal/controller/ship_class.go b/internal/controller/ship_class.go index 6784865..0c9da55 100644 --- a/internal/controller/ship_class.go +++ b/internal/controller/ship_class.go @@ -173,6 +173,7 @@ func (c *Cache) ShipClass(ri int, name string) (*game.ShipType, int, bool) { } func (c *Cache) ShipType(ri int, ID uuid.UUID) (*game.ShipType, bool) { + // TODO: cache + invalidate c.validateRaceIndex(ri) for i := range c.g.Race[ri].ShipTypes { if c.g.Race[ri].ShipTypes[i].ID == ID { diff --git a/internal/controller/ship_group_move.go b/internal/controller/ship_group_move.go index 4686a8d..4395759 100644 --- a/internal/controller/ship_group_move.go +++ b/internal/controller/ship_group_move.go @@ -39,8 +39,11 @@ func (c *Cache) moveShipGroup(i int, delta float64) { } destPlanet := c.MustPlanet(sg.Destination) arrived := false - sg.StateInSpace.X, sg.StateInSpace.Y, arrived = + var x, y float64 + x, y, arrived = util.NextTravelCoord(c.g.Map.Width, c.g.Map.Height, originX, originY, destPlanet.X.F(), destPlanet.Y.F(), delta) + sg.StateInSpace.X = game.F(x) + sg.StateInSpace.Y = game.F(y) if arrived { sg.StateInSpace = nil } diff --git a/internal/controller/ship_group_send.go b/internal/controller/ship_group_send.go index c5cbaa7..0d5b783 100644 --- a/internal/controller/ship_group_send.go +++ b/internal/controller/ship_group_send.go @@ -105,8 +105,8 @@ func (c *Cache) UnsendShips(sg *game.ShipGroup) *game.ShipGroup { func LaunchShips(sg game.ShipGroup, destination uint, originX, originY float64) game.ShipGroup { sg.StateInSpace = &game.InSpace{ Origin: sg.Destination, - X: originX, - Y: originY, + X: game.F(originX), + Y: game.F(originY), } sg.Destination = destination return sg diff --git a/internal/model/game/group.go b/internal/model/game/group.go index 1a054b6..d1c2532 100644 --- a/internal/model/game/group.go +++ b/internal/model/game/group.go @@ -41,13 +41,17 @@ const ( StateTransfer ShipGroupState = "Transfer_Status" ) +func (sgs ShipGroupState) String() string { + return string(sgs) +} + type InSpace struct { - Origin uint `json:"origin"` - X float64 `json:"x"` - Y float64 `json:"y"` + Origin uint `json:"origin"` + X Float `json:"x"` + Y Float `json:"y"` // zero is for Launched status // TODO: calculate range dynamically -BUT- if affects ShipGroup.State() - Range float64 `json:"range"` + Range Float `json:"range"` } func (is InSpace) Equal(other InSpace) bool { @@ -138,6 +142,7 @@ func (sg ShipGroup) State() ShipGroupState { } } +// FIXME: ambigous func, refactor func (sg ShipGroup) OnPlanet() (uint, bool) { switch sg.State() { case StateInOrbit: @@ -152,7 +157,7 @@ func (sg ShipGroup) OnPlanet() (uint, bool) { func (sg ShipGroup) Coord() (float64, float64, bool) { state := sg.State() if state == StateInSpace || state == StateLaunched { - return sg.StateInSpace.X, sg.StateInSpace.Y, true + return sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), true } return 0, 0, false } diff --git a/internal/model/report/battle.go b/internal/model/report/battle.go index dec8c73..f354753 100644 --- a/internal/model/report/battle.go +++ b/internal/model/report/battle.go @@ -7,30 +7,30 @@ import ( ) type BattleReport struct { - ID uuid.UUID `json:"id"` - Planet uint `json:"planet"` - PlanetName string `json:"planetName"` - // PlanetOwnedID uuid.UUID `json:"-"` // TODO: need this? for make report: bombings: initial owher of a planet - Races map[int]uuid.UUID `json:"races"` - Ships map[int]BattleReportGroup `json:"ships"` - Protocol []BattleActionReport `json:"protocol"` + ID uuid.UUID `json:"id"` + Planet uint `json:"planet"` + PlanetName string `json:"planetName"` + Races map[int]uuid.UUID `json:"races"` + Ships map[int]BattleReportGroup `json:"ships"` + Protocol []BattleActionReport `json:"protocol"` } type BattleReportGroup struct { - OwnerID uuid.UUID `json:"-"` // make report: visible ship class - InBattle bool `json:"inBattle"` - Number uint `json:"num"` - NumberLeft uint `json:"numLeft"` - ClassArmament uint `json:"-"` // make report: visible ship class - ClassMass Float `json:"-"` // make report: visible ship class - LoadQuantity Float `json:"loadQuantity"` - DriveTech Float `json:"drive"` - WeaponsTech Float `json:"wwapons"` - ShieldsTech Float `json:"shields"` - CargoTech Float `json:"cargo"` - Race string `json:"race"` - ClassName string `json:"className"` - LoadType string `json:"loadType"` + // OwnerID uuid.UUID `json:"-"` // make report: visible ship class + InBattle bool `json:"inBattle"` + Number uint `json:"num"` + NumberLeft uint `json:"numLeft"` + // ClassArmament uint `json:"-"` // make report: visible ship class + // ClassMass Float `json:"-"` // make report: visible ship class + LoadQuantity Float `json:"loadQuantity"` + Tech map[string]Float `json:"tech"` + // DriveTech Float `json:"drive"` + // WeaponsTech Float `json:"weapons"` + // ShieldsTech Float `json:"shields"` + // CargoTech Float `json:"cargo"` + Race string `json:"race"` + ClassName string `json:"className"` + LoadType string `json:"loadType"` } type BattleActionReport struct { diff --git a/internal/model/report/report.go b/internal/model/report/report.go index 3657acb..13a5686 100644 --- a/internal/model/report/report.go +++ b/internal/model/report/report.go @@ -14,36 +14,36 @@ func F(v float64) Float { } type Report struct { - Version uint `json:"version"` - Turn uint `json:"turn"` - Width uint32 `json:"mapWidth"` - Height uint32 `json:"mapHeight"` - PlanetCount uint32 `json:"mapPlanets"` - Race string `json:"race"` - RaceID uuid.UUID `json:"-"` - Votes Float `json:"votes"` - VoteFor string `json:"voteFor"` - Player []Player `json:"player"` - LocalScience []Science `json:"localScience,omitempty"` - OtherScience []OtherScience `json:"otherScience,omitempty"` - LocalShipClass []ShipClass `json:"localShipClass,omitempty"` - OtherShipClass []OthersShipClass `json:"otherShipClass,omitempty"` - Battle []uuid.UUID `json:"battle,omitempty"` - Bombing []*Bombing `json:"bombing,omitempty"` - IncomingGroup []IncomingGroup `json:"incomingGroup,omitempty"` - LocalPlanet []LocalPlanet `json:"localPlanet,omitempty"` - ShipProduction []ShipProduction `json:"shipProduction,omitempty"` - Route []Route `json:"route,omitempty"` + Version uint `json:"version"` + Turn uint `json:"turn"` + Width uint32 `json:"mapWidth"` + Height uint32 `json:"mapHeight"` + PlanetCount uint32 `json:"mapPlanets"` + Race string `json:"race"` + RaceID uuid.UUID `json:"-"` + Votes Float `json:"votes"` + VoteFor string `json:"voteFor"` + Player []Player `json:"player"` + LocalScience []Science `json:"localScience,omitempty"` + OtherScience []OtherScience `json:"otherScience,omitempty"` + LocalShipClass []ShipClass `json:"localShipClass,omitempty"` + OtherShipClass []OthersShipClass `json:"otherShipClass,omitempty"` + Battle []uuid.UUID `json:"battle,omitempty"` + Bombing []*Bombing `json:"bombing,omitempty"` + IncomingGroup []IncomingGroup `json:"incomingGroup,omitempty"` + LocalPlanet []LocalPlanet `json:"localPlanet,omitempty"` + ShipProduction []ShipProduction `json:"shipProduction,omitempty"` + Route []Route `json:"route,omitempty"` + OtherPlanet []OtherPlanet `json:"otherPlanet,omitempty"` + UninhabitedPlanet []UninhabitedPlanet `json:"uninhabitedPlanet,omitempty"` + UnidentifiedPlanet []UnidentifiedPlanet `json:"unidentifiedPlanet,omitempty"` + LocalFleet []LocalFleet `json:"localFleet,omitempty"` + LocalGroup []LocalGroup `json:"localGroup,omitempty"` + OtherGroup []OtherGroup `json:"otherGroup,omitempty"` + UnidentifiedGroup []UnidentifiedGroup `json:"unidentifiedGroup,omitempty"` - OtherPlanet []OtherPlanet - UninhabitedPlanet []UninhabitedPlanet - UnidentifiedPlanet []UnidentifiedPlanet - Fleet []any // TODO: tbd - LocalShipGroup []any // TODO: tbd - OtherShipGroup []any // TODO: tbd - UnidentifiedGroups []any // TODO: tbd - - PlanetGroupsCache map[uint][]int `json:"-"` + OnPlanetGroupCache map[uint][]int `json:"-"` + InSpaceGroupRangeCache map[int]map[uint]float64 `json:"-"` } type Route struct { diff --git a/internal/model/report/ship.go b/internal/model/report/ship.go index 34f57ac..840dee6 100644 --- a/internal/model/report/ship.go +++ b/internal/model/report/ship.go @@ -30,3 +30,38 @@ type IncomingGroup struct { Speed Float `json:"speed"` Mass Float `json:"mass"` } + +type LocalGroup struct { + OtherGroup + Index uint `json:"index"` + State string `json:"state"` + Fleet *string `json:"fleet"` +} + +type OtherGroup struct { + Number uint `json:"number"` + Class string `json:"class"` + Tech map[string]Float `json:"tech"` + Cargo string `json:"cargo"` + Load Float `json:"load"` + Destination uint `json:"destination"` + Origin *uint `json:"origin,omitempty"` + Range *Float `json:"range,omitempty"` + Speed Float `json:"speed"` + Mass Float `json:"mass"` +} + +type UnidentifiedGroup struct { + X Float `json:"x"` + Y Float `json:"y"` +} + +type LocalFleet struct { + Name string `json:"name"` + Groups uint `json:"groups"` + Destination uint `json:"destination"` + Origin *uint `json:"origin,omitempty"` + Range *Float `json:"range,omitempty"` + Speed Float `json:"speed"` + State string `json:"state"` +}