diff --git a/internal/controller/battle_test.go b/internal/controller/battle_test.go index 3fe3aee..c5d356a 100644 --- a/internal/controller/battle_test.go +++ b/internal/controller/battle_test.go @@ -10,34 +10,28 @@ import ( var ( attacker = game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Attacker", - Drive: 8, - Armament: 1, - Weapons: 8, - Shields: 8, - Cargo: 0, - }, + Name: "Attacker", + Drive: 8, + Armament: 1, + Weapons: 8, + Shields: 8, + Cargo: 0, } defender = game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Defender", - Drive: 1, - Armament: 1, - Weapons: 1, - Shields: 1, - Cargo: 0, - }, + Name: "Defender", + Drive: 1, + Armament: 1, + Weapons: 1, + Shields: 1, + Cargo: 0, } ship = game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Ship", - Drive: 10, - Armament: 1, - Weapons: 10, - Shields: 10, - Cargo: 0, - }, + Name: "Ship", + Drive: 10, + Armament: 1, + Weapons: 10, + Shields: 10, + Cargo: 0, } ) diff --git a/internal/controller/battle_transform.go b/internal/controller/battle_transform.go index 9e747c7..a818e3b 100644 --- a/internal/controller/battle_transform.go +++ b/internal/controller/battle_transform.go @@ -24,17 +24,20 @@ func TransformBattle(c *Cache, b *Battle) *report.BattleReport { sg := c.ShipGroup(groupId) itemNumber := len(r.Ships) r.Ships[itemNumber] = report.BattleReportGroup{ - OwnerID: sg.OwnerID, - InBattle: inBattle, - Number: b.initialNumbers[groupId], - NumberLeft: sg.Number, - ClassName: shipClass.Name, - LoadType: sg.CargoString(), - LoadQuantity: sg.Load.RF(), - Drive: sg.TechLevel(game.TechDrive).RF(), - Weapons: sg.TechLevel(game.TechWeapons).RF(), - Shields: sg.TechLevel(game.TechShields).RF(), - Cargo: sg.TechLevel(game.TechCargo).RF(), + 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()), } cacheShipClass[shipClass.ID] = itemNumber return itemNumber diff --git a/internal/controller/bombing.go b/internal/controller/bombing.go index bff5e58..e8303e9 100644 --- a/internal/controller/bombing.go +++ b/internal/controller/bombing.go @@ -3,37 +3,37 @@ package controller import ( "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/model/game" - "github.com/iliadenisov/galaxy/internal/model/report" ) -func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) report.BombingPlanetReport { +func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) *game.Bombing { attackPower := 0. for _, i := range groups { sg := c.ShipGroup(i) st := c.ShipGroupShipClass(i) attackPower += sg.BombingPower(st) } - r := &report.BombingPlanetReport{ - ID: uuid.New(), - Planet: p.Name, - Number: p.Number, - Owner: c.g.Race[c.RaceIndex(p.Owner)].Name, - Attacker: c.g.Race[ri].Name, - Production: c.PlanetProductionDisplayName(p.Number), - Industry: p.Industry.RF(), - Population: p.Population.RF(), - Colonists: p.Colonists.RF(), - Capital: p.Capital.RF(), - Material: p.Material.RF(), - AttackPower: game.RF(attackPower), + r := &game.Bombing{ + ID: uuid.New(), + PlanetOwnedID: p.Owner, + Planet: p.Name, + Number: p.Number, + Owner: c.g.Race[c.RaceIndex(p.Owner)].Name, + Attacker: c.g.Race[ri].Name, + Production: c.PlanetProductionDisplayName(p.Number), + Industry: p.Industry, + Population: p.Population, + Colonists: p.Colonists, + Capital: p.Capital, + Material: p.Material, + AttackPower: game.F(attackPower), } bombPlanet(p, attackPower) r.Wiped = p.Population == 0 - return *r + return r } -func (c *Cache) ProduceBombings() []report.BombingPlanetReport { - report := make([]report.BombingPlanetReport, 0) +func (c *Cache) ProduceBombings() []*game.Bombing { + report := make([]*game.Bombing, 0) for pn, enemies := range c.collectBombingGroups() { p := c.MustPlanet(pn) for ri, groups := range enemies { diff --git a/internal/controller/bombing_test.go b/internal/controller/bombing_test.go index c172677..6b21110 100644 --- a/internal/controller/bombing_test.go +++ b/internal/controller/bombing_test.go @@ -120,22 +120,22 @@ func TestProduceBombings(t *testing.T) { reports := c.ProduceBombings() assert.Len(t, reports, 2) - for _, r := range reports { - assert.NotEqual(t, uuid.Nil, r.ID) - switch pn := r.Number; pn { + for _, b := range reports { + assert.NotEqual(t, uuid.Nil, b.ID) + switch pn := b.Number; pn { case R1_Planet_1_num: - assert.Equal(t, Race_1.Name, r.Owner) - assert.Equal(t, Race_0.Name, r.Attacker) - assert.InDelta(t, 697.857, r.AttackPower.F(), 0.0003) - assert.True(t, r.Wiped) + assert.Equal(t, Race_1.Name, b.Owner) + assert.Equal(t, Race_0.Name, b.Attacker) + assert.InDelta(t, 697.857, b.AttackPower.F(), 0.0003) + assert.True(t, b.Wiped) assert.Equal(t, uuid.Nil, c.MustPlanet(pn).Owner) assert.Empty(t, c.MustPlanet(pn).Route) assert.Equal(t, 0., c.MustPlanet(pn).Population.F()) case R0_Planet_2_num: - assert.Equal(t, Race_0.Name, r.Owner) - assert.Equal(t, Race_1.Name, r.Attacker) - assert.Equal(t, 358.856, r.AttackPower.F()) - assert.False(t, r.Wiped) + assert.Equal(t, Race_0.Name, b.Owner) + assert.Equal(t, Race_1.Name, b.Attacker) + assert.Equal(t, 358.856, b.AttackPower.F()) + assert.False(t, b.Wiped) assert.Equal(t, Race_0_ID, c.MustPlanet(pn).Owner) assert.NotEmpty(t, c.MustPlanet(pn).Route) assert.InDelta(t, 500.-358.85596, c.MustPlanet(pn).Population.F(), 0.000001) diff --git a/internal/controller/controller.go b/internal/controller/controller.go index d294091..5350139 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -31,7 +31,7 @@ type Repo interface { SaveBattle(t uint, b *report.BattleReport, m *game.BattleMeta) error // SaveBombing stores all prodused bombings for turn t - SaveBombings(t uint, b []report.BombingPlanetReport) error + SaveBombings(t uint, b []*game.Bombing) error } type Controller struct { diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index 4e7eecb..c999264 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -62,14 +62,12 @@ var ( ShipType_Cruiser = "Cruiser" Cruiser = game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Cruiser", - Drive: 15, - Armament: 1, - Weapons: 15, - Shields: 15, - Cargo: 0, - }, + Name: "Cruiser", + Drive: 15, + Armament: 1, + Weapons: 15, + Shields: 15, + Cargo: 0, } ) diff --git a/internal/controller/fleet.go b/internal/controller/fleet.go index 4d86171..0c2f55d 100644 --- a/internal/controller/fleet.go +++ b/internal/controller/fleet.go @@ -50,19 +50,23 @@ func (c *Cache) FleetState(fleetID uuid.UUID) (game.ShipGroupState, *uint, *game } // TODO: Hello! Wanna know fleet's speed? Good. Implement & test this func first. -func (c *Cache) FleetSpeed(fl game.Fleet) float64 { - result := math.MaxFloat64 - for sg := range c.ShipGroupsIndex() { - if c.ShipGroup(sg).FleetID == nil || *c.ShipGroup(sg).FleetID != fl.ID { +func (c *Cache) FleetSpeedAndMass(fi int) (float64, float64) { + c.validateFleetIndex(fi) + speed := math.MaxFloat64 + mass := 0. + for sgi := range c.ShipGroupsIndex() { + if c.ShipGroup(sgi).FleetID == nil || *c.ShipGroup(sgi).FleetID != c.g.Fleets[fi].ID { continue } - st := c.ShipGroupShipClass(sg) - typeSpeed := c.ShipGroup(sg).Speed(st) - if typeSpeed < result { - result = typeSpeed + sg := c.ShipGroup(sgi) + st := c.ShipGroupShipClass(sgi) + typeSpeed := sg.Speed(st) + if typeSpeed < speed { + speed = typeSpeed } + mass += sg.FullMass(st) } - return result + return speed, mass } func (c *Controller) JoinShipGroupToFleet(raceName, fleetName string, group, count uint) error { diff --git a/internal/controller/generate_turn.go b/internal/controller/generate_turn.go index 36c579a..1e34b34 100644 --- a/internal/controller/generate_turn.go +++ b/internal/controller/generate_turn.go @@ -1,12 +1,12 @@ package controller import ( - // "github.com/iliadenisov/galaxy/internal/game/battle" "maps" "slices" "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/model/game" + "github.com/iliadenisov/galaxy/internal/model/report" ) func MakeTurn(c *Controller, r Repo) error { @@ -54,13 +54,30 @@ func MakeTurn(c *Controller, r Repo) error { /*** Last steps ***/ // Store bombings + bombingReport := make([]*report.Bombing, len(bombings)) if len(bombings) > 0 { if err := r.SaveBombings(c.Cache.g.Turn, bombings); err != nil { return err } + for i := range bombings { + bombingReport[i].Planet = bombings[i].Planet + bombingReport[i].PlanetOwnedID = bombings[i].PlanetOwnedID + bombingReport[i].Number = bombings[i].Number + bombingReport[i].Owner = bombings[i].Owner + bombingReport[i].Attacker = bombings[i].Attacker + bombingReport[i].Production = bombings[i].Production + bombingReport[i].Industry = report.F(bombings[i].Industry.F()) + bombingReport[i].Population = report.F(bombings[i].Population.F()) + bombingReport[i].Colonists = report.F(bombings[i].Colonists.F()) + bombingReport[i].Capital = report.F(bombings[i].Capital.F()) + bombingReport[i].Material = report.F(bombings[i].Material.F()) + bombingReport[i].AttackPower = report.F(bombings[i].AttackPower.F()) + bombingReport[i].Wiped = bombings[i].Wiped + } } // Store battles + battleReport := make([]*report.BattleReport, len(battles)) if len(battles) > 0 { battleMeta := make([]game.BattleMeta, len(battles)) for i := range battles { @@ -82,15 +99,21 @@ func MakeTurn(c *Controller, r Repo) error { if err := r.SaveBattle(c.Cache.g.Turn, report, &battleMeta[i]); err != nil { return err } + battleReport[i] = report } } // Remove killed ship groups c.Cache.DeleteKilledShipGroups() - // TODO: Store game state + // Store game state for the new turn and 'current' state as well + r.SaveTurn(c.Cache.g.Turn, c.Cache.g) // TODO: Store individual reports + for ri := range c.Cache.g.Race { + _ = ri + // c.Cache.GenerateReport(ri) + } _ = winners // [ ] monitor memory consumption at this point? diff --git a/internal/controller/planet.go b/internal/controller/planet.go index 6902018..3445099 100644 --- a/internal/controller/planet.go +++ b/internal/controller/planet.go @@ -197,16 +197,38 @@ func (c *Cache) PlanetProductionCapacity(planetNumber uint) float64 { return p.ProductionCapacity() - busyResources } +// TODO: test upgrade & upgrade cancel func (c *Cache) TurnPlanetProductions() { + // cancel upgrade for groups on wiped planets + for sgi := range c.ShipGroupsIndex() { + sg := c.ShipGroup(sgi) + if sg.State() == game.StateUpgrade && c.MustPlanet(sg.Destination).Owner == uuid.Nil { + sg.StateUpgrade = nil + } + } + for pn := range c.listProducingPlanets() { p := c.MustPlanet(pn) ri := c.RaceIndex(p.Owner) r := &c.g.Race[ri] + // upgrade groups and return to in_orbit state + productionAvailable := p.ProductionCapacity() + for sg := range c.shipGroupsInUpgrade(p.Number) { + cost := sg.StateUpgrade.Cost() + if productionAvailable >= cost { + for i := range sg.StateUpgrade.UpgradeTech { + sg.Tech = sg.Tech.Set(sg.StateUpgrade.UpgradeTech[i].Tech, sg.StateUpgrade.UpgradeTech[i].Level) + } + productionAvailable -= cost + } + sg.StateUpgrade = nil + } + switch pt := p.Production.Type; pt { case game.ProductionShip: st := c.MustShipType(ri, *p.Production.SubjectID) - if ships := ProduceShip(p, st.EmptyMass()); ships > 0 { + if ships := ProduceShip(p, productionAvailable, st.EmptyMass()); ships > 0 { c.createShipsUnsafe(ri, st.ID, p.Number, ships) } case game.ResearchScience: @@ -239,7 +261,7 @@ func (c *Cache) TurnPlanetProductions() { func (c *Cache) listProducingPlanets() iter.Seq[uint] { ordered := make([]int, 0) for i := range c.g.Map.Planet { - if c.g.Map.Planet[i].Owner == uuid.Nil || c.g.Map.Planet[i].Production.Type == game.ProductionNone { + if c.g.Map.Planet[i].Owner == uuid.Nil { continue } ordered = append(ordered, i) @@ -276,8 +298,7 @@ func (c *Cache) putMaterial(pn uint, v float64) { c.MustPlanet(pn).Mat(v) } -func ProduceShip(p *game.Planet, shipMass float64) uint { - productionAvailable := p.ProductionCapacity() +func ProduceShip(p *game.Planet, productionAvailable, shipMass float64) uint { if productionAvailable <= 0 { return 0 } diff --git a/internal/controller/planet_test.go b/internal/controller/planet_test.go index 6f5910c..dbd69fc 100644 --- a/internal/controller/planet_test.go +++ b/internal/controller/planet_test.go @@ -167,49 +167,47 @@ func TestProduceShips(t *testing.T) { func TestProduceShip(t *testing.T) { Drone := game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Drone", - Drive: 1, - Armament: 0, - Weapons: 0, - Shields: 0, - Cargo: 0, - }, + Name: "Drone", + Drive: 1, + Armament: 0, + Weapons: 0, + Shields: 0, + Cargo: 0, } BattleShip := game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "BattleShip", - Drive: 25, - Armament: 1, - Weapons: 30, - Shields: 35, - Cargo: 0, - }, + Name: "BattleShip", + Drive: 25, + Armament: 1, + Weapons: 30, + Shields: 35, + Cargo: 0, } p := controller.NewPlanet(0, "Planet_0", uuid.New(), 1, 1, 1000, 1000, 1000, 10, game.ProductionShip.AsType(uuid.Nil)) - r := controller.ProduceShip(&p, Drone.EmptyMass()) + r := controller.ProduceShip(&p, p.ProductionCapacity(), Drone.EmptyMass()) assert.Equal(t, uint(99), r) assert.InDelta(t, 0.0099, *p.Production.Progress, 0.000001) (&p).Production = game.ProductionShip.AsType(uuid.Nil) (&p).Capital = 10. - r = controller.ProduceShip(&p, Drone.EmptyMass()) + r = controller.ProduceShip(&p, p.ProductionCapacity(), Drone.EmptyMass()) assert.Equal(t, uint(100), r) assert.Equal(t, 0., *p.Production.Progress) assert.Equal(t, 0., number.Fixed3(p.Capital.F())) // TODO: zero CAP is not actual '0.0' after series of decrements (&p).Production = game.ProductionShip.AsType(uuid.Nil) (&p).Capital = 0. - r = controller.ProduceShip(&p, BattleShip.EmptyMass()) + r = controller.ProduceShip(&p, p.ProductionCapacity(), BattleShip.EmptyMass()) assert.Equal(t, uint(1), r) assert.InDelta(t, 0.1, *p.Production.Progress, 0.001) (&p).Production = game.ProductionShip.AsType(uuid.Nil) (&p).Capital = 20. - r = controller.ProduceShip(&p, BattleShip.EmptyMass()) + r = controller.ProduceShip(&p, p.ProductionCapacity(), BattleShip.EmptyMass()) assert.Equal(t, uint(1), r) assert.Equal(t, 1./9., *p.Production.Progress) + + // TODO: test with insufficient production capacity } func TestListProducingPlanets(t *testing.T) { diff --git a/internal/controller/report.go b/internal/controller/report.go new file mode 100644 index 0000000..25e091e --- /dev/null +++ b/internal/controller/report.go @@ -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 +} diff --git a/internal/controller/report_test.go b/internal/controller/report_test.go new file mode 100644 index 0000000..1c9c099 --- /dev/null +++ b/internal/controller/report_test.go @@ -0,0 +1,45 @@ +package controller_test + +import ( + "testing" + + "github.com/iliadenisov/galaxy/internal/model/report" + + "github.com/stretchr/testify/assert" +) + +func TestReportLocalShipClass(t *testing.T) { + c, _ := newCache() + + r := &report.Report{} + assert.Len(t, r.LocalShipClass, 0) + + c.ReportLocalShipClass(Race_0_idx, r) + + assert.Len(t, r.LocalShipClass, 3) + for i := range r.LocalShipClass { + assert.NotEmpty(t, r.LocalShipClass[i].Name) + switch n := r.LocalShipClass[i].Name; n { + case Cruiser.Name: + assert.Equal(t, report.F(Cruiser.Drive), r.LocalShipClass[i].Drive) + assert.Equal(t, Cruiser.Armament, r.LocalShipClass[i].Armament) + assert.Equal(t, report.F(Cruiser.Weapons), r.LocalShipClass[i].Weapons) + assert.Equal(t, report.F(Cruiser.Shields), r.LocalShipClass[i].Shields) + assert.Equal(t, report.F(Cruiser.Cargo), r.LocalShipClass[i].Cargo) + case Race_0_Gunship: + assert.Equal(t, report.F(60.), r.LocalShipClass[i].Drive) + assert.Equal(t, uint(3), r.LocalShipClass[i].Armament) + assert.Equal(t, report.F(30.), r.LocalShipClass[i].Weapons) + assert.Equal(t, report.F(100.), r.LocalShipClass[i].Shields) + assert.Equal(t, report.F(0.), r.LocalShipClass[i].Cargo) + case Race_0_Freighter: + assert.Equal(t, report.F(8.), r.LocalShipClass[i].Drive) + assert.Equal(t, uint(0), r.LocalShipClass[i].Armament) + assert.Equal(t, report.F(0.), r.LocalShipClass[i].Weapons) + assert.Equal(t, report.F(2.), r.LocalShipClass[i].Shields) + assert.Equal(t, report.F(10.), r.LocalShipClass[i].Cargo) + default: + assert.Failf(t, "unexpected ship class", "name=%s", n) + } + } +} diff --git a/internal/controller/science.go b/internal/controller/science.go index b496635..e93cfcd 100644 --- a/internal/controller/science.go +++ b/internal/controller/science.go @@ -44,14 +44,12 @@ func (c *Cache) CreateScience(ri int, name string, drive, weapons, shileds, carg return e.NewScienceSumValuesError("D=%f W=%f S=%f C=%f sum=%f", drive, weapons, shileds, cargo, sum) } c.g.Race[ri].Sciences = append(c.g.Race[ri].Sciences, game.Science{ - ID: uuid.New(), - ScienceReport: game.ScienceReport{ - Name: n, - Drive: drive, - Weapons: weapons, - Shields: shileds, - Cargo: cargo, - }, + ID: uuid.New(), + Name: n, + Drive: drive, + Weapons: weapons, + Shields: shileds, + Cargo: cargo, }) return nil } diff --git a/internal/controller/ship_class.go b/internal/controller/ship_class.go index fc10641..6784865 100644 --- a/internal/controller/ship_class.go +++ b/internal/controller/ship_class.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "iter" "slices" "github.com/google/uuid" @@ -30,15 +31,13 @@ func (c *Cache) CreateShipType(ri int, typeName string, drive float64, ammo int, return e.NewEntityTypeNameDuplicateError("ship type %q", c.g.Race[ri].ShipTypes[st].Name) } c.g.Race[ri].ShipTypes = append(c.g.Race[ri].ShipTypes, game.ShipType{ - ID: uuid.New(), - ShipTypeReport: game.ShipTypeReport{ - Name: n, - Drive: drive, - Armament: uint(ammo), - Weapons: weapons, - Shields: shileds, - Cargo: cargo, - }, + ID: uuid.New(), + Name: n, + Drive: drive, + Armament: uint(ammo), + Weapons: weapons, + Shields: shileds, + Cargo: cargo, }) c.invalidateShipGroupCache() c.invalidateFleetCache() @@ -155,6 +154,16 @@ func (c *Cache) ShipTypes(ri int) []*game.ShipType { return result } +func (c *Cache) ListShipTypes(ri int) iter.Seq[*game.ShipType] { + return func(yield func(*game.ShipType) bool) { + for i := range c.g.Race[ri].ShipTypes { + if !yield(&c.g.Race[ri].ShipTypes[i]) { + return + } + } + } +} + func (c *Cache) ShipClass(ri int, name string) (*game.ShipType, int, bool) { i := slices.IndexFunc(c.g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.Name == name }) if i < 0 { diff --git a/internal/controller/ship_group.go b/internal/controller/ship_group.go index 956a133..5d50b8e 100644 --- a/internal/controller/ship_group.go +++ b/internal/controller/ship_group.go @@ -576,10 +576,12 @@ func (c *Cache) listShipGroups(ri int) iter.Seq[*game.ShipGroup] { } } +// TODO: order upgradable groups by cost descending, describe in Rules func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup] { return func(yield func(*game.ShipGroup) bool) { for sg := range c.g.ShipGroups { - if c.g.ShipGroups[sg].Destination == planetNumber && c.g.ShipGroups[sg].State() == game.StateUpgrade { + // number checked for further sanity after battles + if c.g.ShipGroups[sg].Number > 0 && c.g.ShipGroups[sg].Destination == planetNumber && c.g.ShipGroups[sg].State() == game.StateUpgrade { if !yield(&c.g.ShipGroups[sg]) { break } @@ -588,9 +590,9 @@ func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup] } } -func (c *Cache) unsafeDeleteShipGroup(i int) { - c.validateShipGroupIndex(i) - c.g.ShipGroups = append(c.g.ShipGroups[:i], c.g.ShipGroups[i+1:]...) +func (c *Cache) unsafeDeleteShipGroup(sgi int) { + c.validateShipGroupIndex(sgi) + c.g.ShipGroups = append(c.g.ShipGroups[:sgi], c.g.ShipGroups[sgi+1:]...) c.invalidateShipGroupCache() } diff --git a/internal/controller/ship_group_move.go b/internal/controller/ship_group_move.go index cc02dc4..4686a8d 100644 --- a/internal/controller/ship_group_move.go +++ b/internal/controller/ship_group_move.go @@ -18,7 +18,7 @@ func (c *Cache) MoveShipGroups() { if sg.FleetID != nil { fi := c.MustFleetIndex(*sg.FleetID) - delta := c.FleetSpeed(c.g.Fleets[fi]) + delta, _ := c.FleetSpeedAndMass(fi) for fgi := range c.fleetGroupIds(c.RaceIndex(sg.OwnerID), c.MustFleetIndex(*sg.FleetID)) { c.moveShipGroup(fgi, delta) moved[fgi] = true diff --git a/internal/controller/ship_group_upgrade.go b/internal/controller/ship_group_upgrade.go index e027802..bf39628 100644 --- a/internal/controller/ship_group_upgrade.go +++ b/internal/controller/ship_group_upgrade.go @@ -21,14 +21,15 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi return e.NewEntityNotExistsError("group #%d", groupIndex) } st := c.ShipGroupShipClass(sgi) + sg := c.ShipGroup(sgi) - if s := c.ShipGroup(sgi).State(); s != game.StateInOrbit && s != game.StateUpgrade { + if s := sg.State(); s != game.StateInOrbit { // && s != game.StateUpgrade return e.NewShipsBusyError() } - pl := c.MustPlanet(c.ShipGroup(sgi).Destination) - if pl.Owner != uuid.Nil && pl.Owner != c.g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", pl.Number, groupIndex) + p := c.MustPlanet(sg.Destination) + if p.Owner != uuid.Nil && p.Owner != c.g.Race[ri].ID { + return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", p.Number, groupIndex) } upgradeValidTech := map[string]game.Tech{ @@ -65,26 +66,25 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi if c.g.Race[ri].TechLevel(tech) < limitLevel { return e.NewUpgradeTechLevelInsufficientError("%s=%.03f < %.03f", tech.String(), c.g.Race[ri].TechLevel(tech), limitLevel) } - targetLevel[tech] = game.FutureUpgradeLevel(c.g.Race[ri].TechLevel(tech), c.ShipGroup(sgi).TechLevel(tech).F(), limitLevel) + targetLevel[tech] = game.FutureUpgradeLevel(c.g.Race[ri].TechLevel(tech), sg.TechLevel(tech).F(), limitLevel) } else { - targetLevel[tech] = game.CurrentUpgradingLevel(c.g.ShipGroups[sgi], tech) + targetLevel[tech] = game.CurrentUpgradingLevel(sg, tech) } sumLevels += targetLevel[tech] } - productionCapacity := c.PlanetProductionCapacity(pl.Number) - if c.ShipGroup(sgi).State() == game.StateUpgrade { - // to calculate actual capacity we must "compensate" upgrade cost of selected group, if it is in upgrade state - // TODO: this is not tested - productionCapacity += c.ShipGroup(sgi).StateUpgrade.Cost() - } - uc := game.GroupUpgradeCost(*(c.ShipGroup(sgi)), *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo]) + productionCapacity := c.PlanetProductionCapacity(p.Number) + // if sg.State() == game.StateUpgrade { + // // to calculate actual capacity we must "compensate" upgrade cost of selected group, if it is in upgrade state + // productionCapacity += sg.StateUpgrade.Cost() + // } + uc := game.GroupUpgradeCost(sg, *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo]) costForShip := uc.UpgradeCost(1) if costForShip == 0 { return e.NewUpgradeShipsAlreadyUpToDateError("%#v", targetLevel) } - shipsToUpgrade := c.ShipGroup(sgi).Number + shipsToUpgrade := sg.Number // НЕ БОЛЕЕ УКАЗАННОГО if limitShips > 0 && shipsToUpgrade > limitShips { shipsToUpgrade = limitShips @@ -118,16 +118,16 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi } // sanity check - uc = game.GroupUpgradeCost(*(c.ShipGroup(sgi)), *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo]) + uc = game.GroupUpgradeCost(sg, *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo]) costForGroup := uc.UpgradeCost(maxUpgradableShips) if costForGroup > productionCapacity { e.NewGameStateError("cost recalculation: coef=%f cost(%d)=%f L=%f", coef, maxUpgradableShips, costForGroup, productionCapacity) } // break group if needed - if maxUpgradableShips < c.ShipGroup(sgi).Number { - if c.ShipGroup(sgi).State() == game.StateUpgrade { - return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", c.ShipGroup(sgi).Number, maxUpgradableShips) + if maxUpgradableShips < sg.Number { + if sg.State() == game.StateUpgrade { + return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", sg.Number, maxUpgradableShips) } nsgi, err := c.breakGroupSafe(ri, groupIndex, maxUpgradableShips) if err != nil { diff --git a/internal/model/game/bombing.go b/internal/model/game/bombing.go new file mode 100644 index 0000000..a0cf85c --- /dev/null +++ b/internal/model/game/bombing.go @@ -0,0 +1,20 @@ +package game + +import "github.com/google/uuid" + +type Bombing struct { + ID uuid.UUID `json:"-"` + PlanetOwnedID uuid.UUID `json:"-"` // for the report filtering + Planet string `json:"name"` + Number uint `json:"number"` + Owner string `json:"owner"` + Attacker string `json:"attacker"` + Production string `json:"production"` + Industry Float `json:"industry"` // I - Промышленность + Population Float `json:"population"` // P - Население + Colonists Float `json:"colonists"` // COL C - Количество колонистов + Capital Float `json:"capital"` // CAP $ - Запасы промышленности + Material Float `json:"material"` // MAT M - Запасы ресурсов / сырья + AttackPower Float `json:"attack"` + Wiped bool `json:"wiped"` +} diff --git a/internal/model/game/game.go b/internal/model/game/game.go index 6369e50..6282ccc 100644 --- a/internal/model/game/game.go +++ b/internal/model/game/game.go @@ -6,9 +6,22 @@ import ( "maps" "github.com/google/uuid" - "github.com/iliadenisov/galaxy/internal/model/report" ) +type Float float64 + +func F(v float64) Float { + return Float(v) +} + +func (f Float) Add(v float64) Float { + return F(f.F() + v) +} + +func (f Float) F() float64 { + return float64(f) +} + type TechSet map[Tech]float64 func (ts TechSet) Value(t Tech) float64 { @@ -25,6 +38,7 @@ func (ts TechSet) Set(t Tech, v float64) TechSet { return m } +// TODO: turn's incremental Version type Game struct { ID uuid.UUID `json:"id"` Turn uint `json:"turn"` @@ -36,8 +50,8 @@ type Game struct { } type GameMeta struct { - Battles []BattleMeta `json:"battles,omitempty"` - Bombings []report.BombingPlanetReport `json:"bombings,omitempty"` + Battles []BattleMeta `json:"battles,omitempty"` + Bombings []Bombing `json:"bombings,omitempty"` } type BattleMeta struct { @@ -66,10 +80,10 @@ func (g *Game) UnmarshalBinary(data []byte) error { return json.Unmarshal(data, g) } -func (b GameMeta) MarshalBinary() (data []byte, err error) { - return json.Marshal(&b) +func (gm GameMeta) MarshalBinary() (data []byte, err error) { + return json.Marshal(&gm) } -func (b *GameMeta) UnmarshalBinary(data []byte) error { - return json.Unmarshal(data, b) +func (gm *GameMeta) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, gm) } diff --git a/internal/model/game/group.go b/internal/model/game/group.go index ab43bb0..1a054b6 100644 --- a/internal/model/game/group.go +++ b/internal/model/game/group.go @@ -100,21 +100,17 @@ func (t Tech) String() string { } type ShipGroup struct { - Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered) - OwnerID uuid.UUID `json:"ownerId"` // Race link - TypeID uuid.UUID `json:"typeId"` // ShipType link - FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link - Number uint `json:"number"` // Number (quantity) ships of specific ShipType - - CargoType *CargoType `json:"loadType,omitempty"` - Load Float `json:"load"` // Cargo loaded - "Масса груза" - - Tech TechSet `json:"tech"` - - // TODO: TEST: Destination, Origin, Range - Destination uint `json:"destination"` - StateInSpace *InSpace `json:"stateInSpace,omitempty"` - StateUpgrade *InUpgrade `json:"stateUpgrade,omitempty"` + Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered) + OwnerID uuid.UUID `json:"ownerId"` // Race link + TypeID uuid.UUID `json:"typeId"` // ShipType link + FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link + Number uint `json:"number"` // Number (quantity) ships of specific ShipType + CargoType *CargoType `json:"loadType,omitempty"` // + Load Float `json:"load"` // Cargo loaded - "Масса груза" + Tech TechSet `json:"tech"` // + Destination uint `json:"destination"` // TODO: TEST: Destination, Origin, Range + StateInSpace *InSpace `json:"stateInSpace,omitempty"` // + StateUpgrade *InUpgrade `json:"stateUpgrade,omitempty"` // } func (sg ShipGroup) TechLevel(t Tech) Float { diff --git a/internal/model/game/group_test.go b/internal/model/game/group_test.go index c4ea07a..a7e87c0 100644 --- a/internal/model/game/group_test.go +++ b/internal/model/game/group_test.go @@ -13,13 +13,11 @@ import ( func TestCargoCapacity(t *testing.T) { test := func(cargoSize float64, expectCapacity float64) { ship := game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Drive: 1, - Armament: 1, - Weapons: 1, - Shields: 1, - Cargo: cargoSize, - }, + Drive: 1, + Armament: 1, + Weapons: 1, + Shields: 1, + Cargo: cargoSize, } sg := game.ShipGroup{ Number: 1, @@ -41,14 +39,12 @@ func TestCargoCapacity(t *testing.T) { func TestCarryingAndFullMass(t *testing.T) { Freighter := &game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Freighter", - Drive: 8, - Armament: 0, - Weapons: 0, - Shields: 2, - Cargo: 10, - }, + Name: "Freighter", + Drive: 8, + Armament: 0, + Weapons: 0, + Shields: 2, + Cargo: 10, } sg := &game.ShipGroup{ Number: 1, @@ -75,14 +71,12 @@ func TestCarryingAndFullMass(t *testing.T) { func TestSpeed(t *testing.T) { Freighter := &game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Freighter", - Drive: 8, - Armament: 0, - Weapons: 0, - Shields: 2, - Cargo: 10, - }, + Name: "Freighter", + Drive: 8, + Armament: 0, + Weapons: 0, + Shields: 2, + Cargo: 10, } sg := &game.ShipGroup{ Number: 1, @@ -106,14 +100,12 @@ func TestSpeed(t *testing.T) { func TestBombingPower(t *testing.T) { BattleStation := game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Battle_Station", - Drive: 60.0, - Armament: 3, - Weapons: 30.0, - Shields: 100.0, - Cargo: 0.0, - }, + Name: "Battle_Station", + Drive: 60.0, + Armament: 3, + Weapons: 30.0, + Shields: 100.0, + Cargo: 0.0, } sg := game.ShipGroup{ Number: 1, @@ -145,13 +137,11 @@ func TestDriveEffective(t *testing.T) { } for i := range tc { someShip := game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Drive: tc[i].driveShipType, - Armament: rand.UintN(30) + 1, - Weapons: rand.Float64()*30 + 1, - Shields: rand.Float64()*100 + 1, - Cargo: rand.Float64()*20 + 1, - }, + Drive: tc[i].driveShipType, + Armament: rand.UintN(30) + 1, + Weapons: rand.Float64()*30 + 1, + Shields: rand.Float64()*100 + 1, + Cargo: rand.Float64()*20 + 1, } sg := game.ShipGroup{ Number: rand.UintN(4) + 1, diff --git a/internal/model/game/group_upgrade.go b/internal/model/game/group_upgrade.go index 6e28e61..7b72899 100644 --- a/internal/model/game/group_upgrade.go +++ b/internal/model/game/group_upgrade.go @@ -1,7 +1,6 @@ package game import ( - "maps" "math" "slices" ) @@ -12,7 +11,7 @@ type UpgradeCalc struct { func (uc UpgradeCalc) UpgradeCost(ships uint) float64 { var sum float64 - for v := range maps.Values(uc.Cost) { + for _, v := range uc.Cost { sum += v } return sum * float64(ships) @@ -29,7 +28,7 @@ func BlockUpgradeCost(blockMass, currentBlockTech, targetBlockTech float64) floa return (1 - currentBlockTech/targetBlockTech) * 10 * blockMass } -func GroupUpgradeCost(sg ShipGroup, st ShipType, drive, weapons, shields, cargo float64) UpgradeCalc { +func GroupUpgradeCost(sg *ShipGroup, st ShipType, drive, weapons, shields, cargo float64) UpgradeCalc { uc := &UpgradeCalc{Cost: make(map[Tech]float64)} if drive > 0 { uc.Cost[TechDrive] = BlockUpgradeCost(st.DriveBlockMass(), sg.TechLevel(TechDrive).F(), drive) @@ -46,7 +45,7 @@ func GroupUpgradeCost(sg ShipGroup, st ShipType, drive, weapons, shields, cargo return *uc } -func CurrentUpgradingLevel(sg ShipGroup, tech Tech) float64 { +func CurrentUpgradingLevel(sg *ShipGroup, tech Tech) float64 { if sg.StateUpgrade == nil { return 0 } diff --git a/internal/model/game/group_upgrade_test.go b/internal/model/game/group_upgrade_test.go index ab45114..84a8f99 100644 --- a/internal/model/game/group_upgrade_test.go +++ b/internal/model/game/group_upgrade_test.go @@ -9,14 +9,12 @@ import ( var ( Cruiser = game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Cruiser", - Drive: 15, - Armament: 1, - Weapons: 15, - Shields: 15, - Cargo: 0, - }, + Name: "Cruiser", + Drive: 15, + Armament: 1, + Weapons: 15, + Shields: 15, + Cargo: 0, } ) @@ -27,7 +25,7 @@ func TestBlockUpgradeCost(t *testing.T) { } func TestGroupUpgradeCost(t *testing.T) { - sg := game.ShipGroup{ + sg := &game.ShipGroup{ Tech: map[game.Tech]float64{ game.TechDrive: 1.0, game.TechWeapons: 1.0, @@ -40,7 +38,7 @@ func TestGroupUpgradeCost(t *testing.T) { } func TestUpgradeMaxShips(t *testing.T) { - sg := game.ShipGroup{ + sg := &game.ShipGroup{ Tech: map[game.Tech]float64{ game.TechDrive: 1.0, game.TechWeapons: 1.0, @@ -57,26 +55,26 @@ func TestCurrentUpgradingLevel(t *testing.T) { sg := &game.ShipGroup{ StateUpgrade: nil, } - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechDrive)) - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechWeapons)) - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechShields)) - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechCargo)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechDrive)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechWeapons)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechShields)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechCargo)) sg.StateUpgrade = &game.InUpgrade{ UpgradeTech: []game.UpgradePreference{ {Tech: game.TechDrive, Level: 1.5, Cost: 100.1}, }, } - assert.Equal(t, 1.5, game.CurrentUpgradingLevel(*sg, game.TechDrive)) - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechWeapons)) - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechShields)) - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechCargo)) + assert.Equal(t, 1.5, game.CurrentUpgradingLevel(sg, game.TechDrive)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechWeapons)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechShields)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechCargo)) sg.StateUpgrade.UpgradeTech = append(sg.StateUpgrade.UpgradeTech, game.UpgradePreference{Tech: game.TechCargo, Level: 2.2, Cost: 200.2}) - assert.Equal(t, 1.5, game.CurrentUpgradingLevel(*sg, game.TechDrive)) - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechWeapons)) - assert.Equal(t, 0.0, game.CurrentUpgradingLevel(*sg, game.TechShields)) - assert.Equal(t, 2.2, game.CurrentUpgradingLevel(*sg, game.TechCargo)) + assert.Equal(t, 1.5, game.CurrentUpgradingLevel(sg, game.TechDrive)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechWeapons)) + assert.Equal(t, 0.0, game.CurrentUpgradingLevel(sg, game.TechShields)) + assert.Equal(t, 2.2, game.CurrentUpgradingLevel(sg, game.TechCargo)) } func TestFutureUpgradeLevel(t *testing.T) { diff --git a/internal/model/game/planet.go b/internal/model/game/planet.go index cede4ba..727adc3 100644 --- a/internal/model/game/planet.go +++ b/internal/model/game/planet.go @@ -27,9 +27,9 @@ type PlanetReport struct { Population Float `json:"population"` // P - Население Colonists Float `json:"colonists"` // COL C - Количество колонистов Production Production `json:"production"` // TODO: internal/report format - // Параметр "L" - Свободный производственный потенциал } +// TODO: unwrap in one struct type Planet struct { Owner uuid.UUID `json:"owner"` // FIXME: nil value when no owner Route map[RouteType]uint `json:"route"` diff --git a/internal/model/game/race.go b/internal/model/game/race.go index f68423f..95a1233 100644 --- a/internal/model/game/race.go +++ b/internal/model/game/race.go @@ -20,6 +20,10 @@ type Race struct { type Relation string +func (r Relation) String() string { + return string(r) +} + const ( RelationWar Relation = "War" RelationPeace Relation = "Peace" diff --git a/internal/model/game/report.go b/internal/model/game/report.go deleted file mode 100644 index af8431e..0000000 --- a/internal/model/game/report.go +++ /dev/null @@ -1,67 +0,0 @@ -package game - -type Report struct { - Width, Height uint32 - PlanetCount uint32 // do we need that? - PlayersLeft uint32 // do we need that? - - Votes float64 - VoteFor string - - Statuses []PlayerStatus - - Sciences []ScienceReport - ForeignSciences []ScienceReportForeign - - ShipTypes []ShipTypeReport - ForeignShipTypes []ShipTypeReportForeign - - Battles []any // TODO: tbd - - Bombings []any // TODO: tbd - - IncomingGroups []IncomingGroup - - Planets []PlanetReport - ForeignPlanets []PlanetReportForeign - UninhabitedPlanets []UninhabitedPlanet - UnidentifiedPlanets []UnidentifiedPlanet - - ShipsInProduction []any // TODO: tbd - - Routes []any // TODO: tbd - - Fleets []any // TODO: tbd - - ShipGroups []any // TODO: tbd - - ForeignShipGroups []any // TODO: tbd - - UnidentifiedGroups []any // TODO: tbd -} - -type IncomingGroup struct { - SourcePlanetNumber uint - TargetPlanetNumber uint - Distance float64 - Speed float64 - Mass float64 -} - -type ReportRelation struct { - RaceName string - Relation string -} - -type PlayerStatus struct { - Name string - Drive float64 `json:"drive"` - Weapons float64 `json:"weapons"` - Shields float64 `json:"shields"` - Cargo float64 `json:"cargo"` - Population float64 - Industry float64 - Planets uint16 - Relation ReportRelation - Votes float64 -} diff --git a/internal/model/game/science.go b/internal/model/game/science.go index 4ebc63b..857a6f5 100644 --- a/internal/model/game/science.go +++ b/internal/model/game/science.go @@ -5,19 +5,10 @@ import ( ) type Science struct { - ID uuid.UUID `json:"id"` - ScienceReport -} - -type ScienceReportForeign struct { - RaceName string - ScienceReport -} - -type ScienceReport struct { - Name string `json:"name"` - Drive float64 `json:"drive"` - Weapons float64 `json:"weapons"` - Shields float64 `json:"shields"` - Cargo float64 `json:"cargo"` + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Drive float64 `json:"drive"` + Weapons float64 `json:"weapons"` + Shields float64 `json:"shields"` + Cargo float64 `json:"cargo"` } diff --git a/internal/model/game/ship.go b/internal/model/game/ship.go index 5699f69..0132ddb 100644 --- a/internal/model/game/ship.go +++ b/internal/model/game/ship.go @@ -4,23 +4,14 @@ import ( "github.com/google/uuid" ) -type ShipTypeReport struct { - Name string `json:"name"` - Drive float64 `json:"drive"` - Armament uint `json:"armament"` - Weapons float64 `json:"weapons"` - Shields float64 `json:"shields"` - Cargo float64 `json:"cargo"` -} - type ShipType struct { - ID uuid.UUID `json:"id"` - ShipTypeReport -} - -type ShipTypeReportForeign struct { - RaceName string - ShipTypeReport + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Drive float64 `json:"drive"` + Armament uint `json:"armament"` + Weapons float64 `json:"weapons"` + Shields float64 `json:"shields"` + Cargo float64 `json:"cargo"` } func (st ShipType) Equal(o ShipType) bool { diff --git a/internal/model/game/ship_test.go b/internal/model/game/ship_test.go index a7d8eec..c5c0210 100644 --- a/internal/model/game/ship_test.go +++ b/internal/model/game/ship_test.go @@ -9,38 +9,32 @@ import ( func TestEmptyMass(t *testing.T) { Freighter := game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Freighter", - Drive: 8, - Armament: 0, - Weapons: 0, - Shields: 2, - Cargo: 10, - }, + Name: "Freighter", + Drive: 8, + Armament: 0, + Weapons: 0, + Shields: 2, + Cargo: 10, } assert.Equal(t, 20., Freighter.EmptyMass()) Gunship := game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Gunship", - Drive: 4, - Armament: 2, - Weapons: 2, - Shields: 4, - Cargo: 0, - }, + Name: "Gunship", + Drive: 4, + Armament: 2, + Weapons: 2, + Shields: 4, + Cargo: 0, } assert.Equal(t, 11., Gunship.EmptyMass()) Cruiser := game.ShipType{ - ShipTypeReport: game.ShipTypeReport{ - Name: "Cruiser", - Drive: 15, - Armament: 1, - Weapons: 15, - Shields: 15, - Cargo: 0, - }, + Name: "Cruiser", + Drive: 15, + Armament: 1, + Weapons: 15, + Shields: 15, + Cargo: 0, } assert.Equal(t, 45., Cruiser.EmptyMass()) } diff --git a/internal/model/report/battle.go b/internal/model/report/battle.go index 5889701..dec8c73 100644 --- a/internal/model/report/battle.go +++ b/internal/model/report/battle.go @@ -7,27 +7,30 @@ import ( ) type BattleReport struct { - ID uuid.UUID `json:"id"` - Turn uint `json:"turn"` - Planet uint `json:"planet"` - PlanetName string `json:"planet_name"` - 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"` + // 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"` } type BattleReportGroup struct { - OwnerID uuid.UUID `json:"ownerId"` - InBattle bool `json:"inBattle"` - Number uint `json:"num"` - NumberLeft uint `json:"numLeft"` - ClassName string `json:"className"` - LoadType string `json:"loadType"` - LoadQuantity Float `json:"loadQuantity"` - Drive Float `json:"drive"` - Weapons Float `json:"wwapons"` - Shields Float `json:"shields"` - Cargo Float `json:"cargo"` + 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"` } type BattleActionReport struct { diff --git a/internal/model/report/bombing.go b/internal/model/report/bombing.go index 2903a01..7374601 100644 --- a/internal/model/report/bombing.go +++ b/internal/model/report/bombing.go @@ -2,18 +2,18 @@ package report import "github.com/google/uuid" -type BombingPlanetReport struct { - ID uuid.UUID `json:"id"` - Planet string `json:"name"` - Number uint `json:"number"` - Owner string `json:"owner"` - Attacker string `json:"attacker"` - Production string `json:"production"` - Industry Float `json:"industry"` // I - Промышленность - Population Float `json:"population"` // P - Население - Colonists Float `json:"colonists"` // COL C - Количество колонистов - Capital Float `json:"capital"` // CAP $ - Запасы промышленности - Material Float `json:"material"` // MAT M - Запасы ресурсов / сырья - AttackPower Float `json:"attack"` - Wiped bool `json:"wiped"` +type Bombing struct { + PlanetOwnedID uuid.UUID `json:"-"` // make report: filter by planet's owner before bombing + Number uint `json:"planet"` + Planet string `json:"planetName"` + Owner string `json:"owner"` + Attacker string `json:"attacker"` + Production string `json:"production"` + Industry Float `json:"industry"` // I - Промышленность + Population Float `json:"population"` // P - Население + Colonists Float `json:"colonists"` // COL C - Количество колонистов + Capital Float `json:"capital"` // CAP $ - Запасы промышленности + Material Float `json:"material"` // MAT M - Запасы ресурсов / сырья + AttackPower Float `json:"attack"` + Wiped bool `json:"wiped"` } diff --git a/internal/model/report/planet.go b/internal/model/report/planet.go new file mode 100644 index 0000000..a9d46aa --- /dev/null +++ b/internal/model/report/planet.go @@ -0,0 +1,30 @@ +package report + +type OtherPlanet struct { + Owner string `json:"owner"` + LocalPlanet +} + +type LocalPlanet struct { + UninhabitedPlanet + Industry Float `json:"industry"` // I - Промышленность + Population Float `json:"population"` // P - Население + Colonists Float `json:"colonists"` // COL C - Количество колонистов + Production string `json:"production"` + FreeIndustry Float `json:"freeInductry"` // Параметр "L" - Свободный производственный потенциал +} + +type UninhabitedPlanet struct { + UnidentifiedPlanet + Size Float `json:"size"` + Name string `json:"name"` + Resources Float `json:"resources"` // R - Ресурсы + Capital Float `json:"capital"` // CAP $ - Запасы промышленности + Material Float `json:"material"` // MAT M - Запасы ресурсов / сырьё +} + +type UnidentifiedPlanet struct { + X Float `json:"x"` + Y Float `json:"y"` + Number uint `json:"number"` +} diff --git a/internal/model/report/report.go b/internal/model/report/report.go new file mode 100644 index 0000000..3657acb --- /dev/null +++ b/internal/model/report/report.go @@ -0,0 +1,74 @@ +package report + +import ( + "encoding/json" + + "github.com/google/uuid" + n "github.com/iliadenisov/galaxy/internal/number" +) + +type Float float64 + +func F(v float64) Float { + return Float(n.Fixed3(v)) +} + +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"` + + 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:"-"` +} + +type Route struct { + Planet uint `json:"planet"` + Route map[uint]string `json:"route"` +} + +type Player struct { + ID uuid.UUID `json:"-"` + Name string `json:"name"` + Drive Float `json:"drive"` + Weapons Float `json:"weapons"` + Shields Float `json:"shields"` + Cargo Float `json:"cargo"` + Population Float `json:"population"` + Industry Float `json:"industry"` + Planets uint16 `json:"planets"` + Relation string `json:"relation"` + Votes Float `json:"votes"` +} + +func (r Report) MarshalBinary() (data []byte, err error) { + return json.Marshal(&r) +} + +func (r *Report) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, r) +} diff --git a/internal/model/report/science.go b/internal/model/report/science.go new file mode 100644 index 0000000..cfbffdd --- /dev/null +++ b/internal/model/report/science.go @@ -0,0 +1,14 @@ +package report + +type Science struct { + Name string `json:"name"` + Drive Float `json:"drive"` + Weapons Float `json:"weapons"` + Shields Float `json:"shields"` + Cargo Float `json:"cargo"` +} + +type OtherScience struct { + Race string `json:"race"` + Science +} diff --git a/internal/model/report/ship.go b/internal/model/report/ship.go new file mode 100644 index 0000000..34f57ac --- /dev/null +++ b/internal/model/report/ship.go @@ -0,0 +1,32 @@ +package report + +type ShipClass struct { + Name string `json:"name"` + Drive Float `json:"drive"` + Armament uint `json:"armament"` + Weapons Float `json:"weapons"` + Shields Float `json:"shields"` + Cargo Float `json:"cargo"` + Mass Float `json:"mass"` +} + +type OthersShipClass struct { + Race string `json:"race"` + ShipClass +} + +type ShipProduction struct { + Planet uint `json:"planet"` // Галактический номер планеты + Class string `json:"class"` // Наименование типа строящегося корабля + Cost Float `json:"cost"` // Стоимость постройки одного такого корабля (в производственных ед.) без учета расходов на добычу сырья + Wasted Float `json:"wasted"` // Сколько производственных единиц уже было затрачено на постройку этого корабля (уже учитывая производство сырья) + Free Float `json:"free"` // Свободный производственный потенциал +} + +type IncomingGroup struct { + Origin uint `json:"origin"` + Destination uint `json:"destination"` + Distance Float `json:"distance"` + Speed Float `json:"speed"` + Mass Float `json:"mass"` +} diff --git a/internal/repo/game.go b/internal/repo/game.go index 6f3a769..21170ee 100644 --- a/internal/repo/game.go +++ b/internal/repo/game.go @@ -141,12 +141,14 @@ func saveBattle(s Storage, t uint, b *report.BattleReport) error { return nil } -func (r *repo) SaveBombings(t uint, b []report.BombingPlanetReport) error { +func (r *repo) SaveBombings(t uint, b []*game.Bombing) error { meta, err := loadMeta(r.s) if err != nil { return err } - meta.Bombings = b + for i := range b { + meta.Bombings = append(meta.Bombings, *b[i]) + } return saveMeta(r.s, t, meta) }