From 6a603ea9ad83679c0c43a7c3305617b499c649f9 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Wed, 4 Feb 2026 18:33:38 +0200 Subject: [PATCH] refactor: floats, tests --- internal/controller/battle.go | 4 +- internal/controller/battle_test.go | 16 +-- internal/controller/battle_transform.go | 2 +- internal/controller/bombing_test.go | 2 +- internal/controller/controller_test.go | 12 +- internal/controller/generate_game.go | 2 +- internal/controller/planet.go | 18 +-- internal/controller/planet_test.go | 18 ++- internal/controller/report.go | 40 +++--- internal/controller/report_test.go | 8 +- internal/controller/science.go | 8 +- internal/controller/science_test.go | 16 +-- internal/controller/ship_class.go | 8 +- internal/controller/ship_group.go | 18 +-- internal/controller/ship_group_send_test.go | 8 +- internal/controller/ship_group_test.go | 2 +- internal/controller/ship_group_upgrade.go | 106 ++++++++++++-- .../controller/ship_group_upgrade_test.go | 117 ++++++++++++++- internal/controller/vote.go | 7 +- internal/error/generic.go | 3 - internal/error/input.go | 4 - internal/game/cmd_ship_type_test.go | 8 +- internal/game/controller_test.go | 2 +- internal/model/game/fleet_send.go | 80 ----------- internal/model/game/fleet_send_test.go | 73 ---------- internal/model/game/fleet_test.go | 136 ------------------ internal/model/game/game.go | 8 +- internal/model/game/game_export_test.go | 25 ---- internal/model/game/group.go | 24 ++-- internal/model/game/group_test.go | 66 ++++----- internal/model/game/group_upgrade.go | 90 ------------ internal/model/game/group_upgrade_test.go | 121 ---------------- internal/model/game/production.go | 2 +- internal/model/game/race.go | 18 +-- internal/model/game/science.go | 8 +- internal/model/game/settings.go | 7 - internal/model/game/ship.go | 16 +-- 37 files changed, 381 insertions(+), 722 deletions(-) delete mode 100644 internal/model/game/fleet_send.go delete mode 100644 internal/model/game/fleet_send_test.go delete mode 100644 internal/model/game/fleet_test.go delete mode 100644 internal/model/game/game_export_test.go delete mode 100644 internal/model/game/group_upgrade.go delete mode 100644 internal/model/game/group_upgrade_test.go delete mode 100644 internal/model/game/settings.go diff --git a/internal/controller/battle.go b/internal/controller/battle.go index f0027c4..5af77e5 100644 --- a/internal/controller/battle.go +++ b/internal/controller/battle.go @@ -64,9 +64,9 @@ func FilterBattleOpponents(c *Cache, attIdx, defIdx int, cacheProbability map[in } p := DestructionProbability( - c.ShipGroupShipClass(attIdx).Weapons, + c.ShipGroupShipClass(attIdx).Weapons.F(), c.ShipGroup(attIdx).TechLevel(game.TechWeapons).F(), - c.ShipGroupShipClass(defIdx).Shields, + c.ShipGroupShipClass(defIdx).Shields.F(), c.ShipGroup(defIdx).TechLevel(game.TechShields).F(), c.ShipGroup(defIdx).FullMass(c.ShipGroupShipClass(defIdx)), ) diff --git a/internal/controller/battle_test.go b/internal/controller/battle_test.go index c5d356a..b716920 100644 --- a/internal/controller/battle_test.go +++ b/internal/controller/battle_test.go @@ -36,25 +36,25 @@ var ( ) func TestDestructionProbability(t *testing.T) { - probability := controller.DestructionProbability(ship.Weapons, 1, ship.Shields, 1, ship.EmptyMass()) + probability := controller.DestructionProbability(ship.Weapons.F(), 1, ship.Shields.F(), 1, ship.EmptyMass()) assert.Equal(t, .5, probability) undefeatedShip := ship undefeatedShip.Shields = 55 - probability = controller.DestructionProbability(ship.Weapons, 1, undefeatedShip.Shields, 1, undefeatedShip.EmptyMass()) + probability = controller.DestructionProbability(ship.Weapons.F(), 1, undefeatedShip.Shields.F(), 1, undefeatedShip.EmptyMass()) assert.LessOrEqual(t, probability, 0.) disruptiveShip := ship disruptiveShip.Weapons = 40 - probability = controller.DestructionProbability(disruptiveShip.Weapons, 1, ship.Shields, 1, ship.EmptyMass()) + probability = controller.DestructionProbability(disruptiveShip.Weapons.F(), 1, ship.Shields.F(), 1, ship.EmptyMass()) assert.GreaterOrEqual(t, probability, 1.) } func TestEffectiveDefence(t *testing.T) { - assert.Equal(t, 10., controller.EffectiveDefence(ship.Shields, 1, ship.EmptyMass())) + assert.Equal(t, 10., controller.EffectiveDefence(ship.Shields.F(), 1, ship.EmptyMass())) - attackerEffectiveDefence := controller.EffectiveDefence(attacker.Shields, 1, attacker.EmptyMass()) - defenderEffectiveDefence := controller.EffectiveDefence(defender.Shields, 1, defender.EmptyMass()) + attackerEffectiveDefence := controller.EffectiveDefence(attacker.Shields.F(), 1, attacker.EmptyMass()) + defenderEffectiveDefence := controller.EffectiveDefence(defender.Shields.F(), 1, defender.EmptyMass()) // attacker's effective shields must be 'just' 4 times greater than defender's assert.InDelta(t, defenderEffectiveDefence*4, attackerEffectiveDefence, 0) @@ -95,7 +95,7 @@ func TestFilterBattleOpponents(t *testing.T) { assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 15)) // 2 undefeatedShip := ship undefeatedShip.Shields = 100 - assert.NoError(t, c.CreateShipType(Race_1_idx, undefeatedShip.Name, undefeatedShip.Drive, int(undefeatedShip.Armament), undefeatedShip.Weapons, undefeatedShip.Shields, undefeatedShip.Cargo)) + assert.NoError(t, c.CreateShipType(Race_1_idx, undefeatedShip.Name, undefeatedShip.Drive.F(), int(undefeatedShip.Armament), undefeatedShip.Weapons.F(), undefeatedShip.Shields.F(), undefeatedShip.Cargo.F())) assert.NoError(t, c.CreateShips(Race_1_idx, undefeatedShip.Name, R1_Planet_1_num, 1)) // 3 cacheProbability := make(map[int]map[int]float64) @@ -120,7 +120,7 @@ func TestFilterBattleOpponents(t *testing.T) { assert.True(t, controller.FilterBattleOpponents(c, 2, 0, cacheProbability)) assert.NoError(t, c.UpdateRelation(Race_0_idx, Race_1_idx, game.RelationWar)) - assert.LessOrEqual(t, controller.DestructionProbability(Cruiser.Weapons, 1, undefeatedShip.Shields, 1, undefeatedShip.EmptyMass()), 0.) + assert.LessOrEqual(t, controller.DestructionProbability(Cruiser.Weapons.F(), 1, undefeatedShip.Shields.F(), 1, undefeatedShip.EmptyMass()), 0.) assert.True(t, controller.FilterBattleOpponents(c, 1, 3, cacheProbability)) assert.NotContains(t, cacheProbability[1], 3) } diff --git a/internal/controller/battle_transform.go b/internal/controller/battle_transform.go index 477c646..5026644 100644 --- a/internal/controller/battle_transform.go +++ b/internal/controller/battle_transform.go @@ -39,7 +39,7 @@ func TransformBattle(c *Cache, b *Battle) *report.BattleReport { // CargoTech: report.F(sg.TechLevel(game.TechCargo).F()), } for t, v := range sg.Tech { - bg.Tech[t.String()] = report.F(v) + bg.Tech[t.String()] = report.F(v.F()) } r.Ships[itemNumber] = *bg cacheShipClass[shipClass.ID] = itemNumber diff --git a/internal/controller/bombing_test.go b/internal/controller/bombing_test.go index 6b21110..b9e7462 100644 --- a/internal/controller/bombing_test.go +++ b/internal/controller/bombing_test.go @@ -134,7 +134,7 @@ func TestProduceBombings(t *testing.T) { case R0_Planet_2_num: 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.InDelta(t, 358.856, b.AttackPower.F(), 0.0001) assert.False(t, b.Wiped) assert.Equal(t, Race_0_ID, c.MustPlanet(pn).Owner) assert.NotEmpty(t, c.MustPlanet(pn).Route) diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index c999264..b737705 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -16,7 +16,7 @@ var ( ID: Race_0_ID, VoteFor: Race_0_ID, Name: "Race_0", - Tech: map[game.Tech]float64{ + Tech: map[game.Tech]game.Float{ game.TechDrive: 1.1, game.TechWeapons: 1.2, game.TechShields: 1.3, @@ -28,7 +28,7 @@ var ( ID: Race_1_ID, VoteFor: Race_1_ID, Name: "Race_1", - Tech: map[game.Tech]float64{ + Tech: map[game.Tech]game.Float{ game.TechDrive: 2.1, game.TechWeapons: 2.2, game.TechShields: 2.3, @@ -95,9 +95,9 @@ func newGame() *game.Game { Width: 1000, Height: 1000, Planet: []game.Planet{ - controller.NewPlanet(R0_Planet_0_num, "Planet_0", Race_0.ID, 1, 1, 100, 100, 100, 0, game.ProductionNone.AsType(uuid.Nil)), - controller.NewPlanet(R1_Planet_1_num, "Planet_1", Race_1.ID, 2, 2, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)), - controller.NewPlanet(R0_Planet_2_num, "Planet_2", Race_0.ID, 3, 3, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)), + controller.NewPlanet(R0_Planet_0_num, "Planet_0", Race_0.ID, 1, 1, 100, 100, 100, 0, game.ProductionCapital.AsType(uuid.Nil)), + controller.NewPlanet(R1_Planet_1_num, "Planet_1", Race_1.ID, 2, 2, 100, 0, 0, 0, game.ProductionCapital.AsType(uuid.Nil)), + controller.NewPlanet(R0_Planet_2_num, "Planet_2", Race_0.ID, 3, 3, 100, 0, 0, 0, game.ProductionCapital.AsType(uuid.Nil)), controller.NewPlanet(Uninhabited_Planet_3_num, "Planet_3", uuid.Nil, 500, 500, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)), controller.NewPlanet(Uninhabited_Planet_4_num, "Planet_4", uuid.Nil, 10, 10, 500, 0, 0, 10, game.ProductionNone.AsType(uuid.Nil)), }, @@ -111,7 +111,7 @@ func newCache() (*controller.Cache, *controller.Controller) { c := controller.NewCache(g) assertNoError(c.CreateShipType(Race_0_idx, Race_0_Gunship, 60, 3, 30, 100, 0)) assertNoError(c.CreateShipType(Race_0_idx, Race_0_Freighter, 8, 0, 0, 2, 10)) - assertNoError(c.CreateShipType(Race_0_idx, ShipType_Cruiser, Cruiser.Drive, int(Cruiser.Armament), Cruiser.Weapons, Cruiser.Shields, Cruiser.Cargo)) + assertNoError(c.CreateShipType(Race_0_idx, ShipType_Cruiser, Cruiser.Drive.F(), int(Cruiser.Armament), Cruiser.Weapons.F(), Cruiser.Shields.F(), Cruiser.Cargo.F())) assertNoError(c.CreateShipType(Race_1_idx, Race_1_Gunship, 60, 3, 30, 100, 0)) assertNoError(c.CreateShipType(Race_1_idx, Race_1_Freighter, 8, 0, 0, 2, 10)) diff --git a/internal/controller/generate_game.go b/internal/controller/generate_game.go index 3c75b63..d38ee72 100644 --- a/internal/controller/generate_game.go +++ b/internal/controller/generate_game.go @@ -61,7 +61,7 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) { ID: raceID, Name: races[i], VoteFor: raceID, - Tech: map[game.Tech]float64{ + Tech: map[game.Tech]game.Float{ game.TechDrive: 1, game.TechWeapons: 1, game.TechShields: 1, diff --git a/internal/controller/planet.go b/internal/controller/planet.go index 3445099..764ca8c 100644 --- a/internal/controller/planet.go +++ b/internal/controller/planet.go @@ -108,11 +108,11 @@ func (c *Cache) PlanetProduction(ri int, number int, prod game.ProductionType, s if p.Production.Type == game.ProductionShip && (prod != game.ProductionShip || *subjectID != *p.Production.SubjectID) { mat, _ := c.MustShipType(ri, *p.Production.SubjectID).ProductionCost() - p.Mat(p.Material.F() + mat*(*p.Production.Progress)) + p.Mat(p.Material.F() + mat*(*p.Production.Progress).F()) *p.Production.Progress = 0. } else if prod == game.ProductionShip { // new ship class to produce; otherwise we must have been returned from the func earlier - var progress float64 = 0. + progress := game.F(0.) p.Production.Progress = &progress } @@ -218,7 +218,7 @@ func (c *Cache) TurnPlanetProductions() { 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) + sg.Tech = sg.Tech.Set(sg.StateUpgrade.UpgradeTech[i].Tech, sg.StateUpgrade.UpgradeTech[i].Level.F()) } productionAvailable -= cost } @@ -233,7 +233,7 @@ func (c *Cache) TurnPlanetProductions() { } case game.ResearchScience: sc := c.mustScience(ri, *p.Production.SubjectID) - ResearchTech(r, p.ProductionCapacity(), sc.Drive, sc.Weapons, sc.Shields, sc.Cargo) + ResearchTech(r, p.ProductionCapacity(), sc.Drive.F(), sc.Weapons.F(), sc.Shields.F(), sc.Cargo.F()) case game.ResearchDrive: ResearchTech(r, p.ProductionCapacity(), 1., 0, 0, 0) case game.ResearchWeapons: @@ -247,7 +247,7 @@ func (c *Cache) TurnPlanetProductions() { case game.ProductionCapital: p.ProduceIndustry() default: - panic(fmt.Sprintf("unprocessed production type: %v", pt)) + panic(fmt.Sprintf("unprocessed production type: '%v' for planet: #%d owner=%v", pt, pn, p.Owner)) } // last step: increase population / colonists @@ -261,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 { + if c.g.Map.Planet[i].Owner == uuid.Nil || c.g.Map.Planet[i].Production.Type == game.ProductionNone { continue } ordered = append(ordered, i) @@ -305,7 +305,7 @@ func ProduceShip(p *game.Planet, productionAvailable, shipMass float64) uint { CAP_perShip := shipMass / p.Resources.F() productionForMass := shipMass * 10. ships := uint(0) - flZero := 0. + flZero := game.F(0.) p.Production.Progress = &flZero for productionAvailable > 0 { var productionExtraCAP float64 @@ -318,8 +318,8 @@ func ProduceShip(p *game.Planet, productionAvailable, shipMass float64) uint { p.Cap(p.Capital.F() - (CAP_perShip - productionExtraCAP)) ships++ } else { - progress := productionAvailable / productionCost - productionAvailable -= productionCost * progress + progress := game.F(productionAvailable / productionCost) + productionAvailable -= productionCost * progress.F() p.Production.Progress = &progress break } diff --git a/internal/controller/planet_test.go b/internal/controller/planet_test.go index dbd69fc..bf90033 100644 --- a/internal/controller/planet_test.go +++ b/internal/controller/planet_test.go @@ -145,14 +145,14 @@ func TestProduceShips(t *testing.T) { assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 0) assert.NotNil(t, c.MustPlanet(R0_Planet_0_num).Production.Progress) progress := *c.MustPlanet(R0_Planet_0_num).Production.Progress - assert.InDelta(t, 0.45, progress, 0.001) + assert.InDelta(t, 0.45, progress.F(), 0.001) assert.NoError(t, c.CreateShipType(Race_0_idx, "Drone", 1, 0, 0, 0, 0)) assert.NoError(t, c.CreateShips(Race_0_idx, "Drone", uint(pn), 7)) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 1) assert.Equal(t, uint(7), c.ShipGroup(0).Number) assert.NoError(t, g.PlanetProduction(Race_0.Name, pn, "SHIP", "Drone")) - assert.InDelta(t, shipMass*progress, c.MustPlanet(R0_Planet_0_num).Material.F(), 0.00001) // 99.(0099) material build + assert.InDelta(t, shipMass*progress.F(), c.MustPlanet(R0_Planet_0_num).Material.F(), 0.00001) // 99.(0099) material build c.MustPlanet(R0_Planet_0_num).Material = 0 c.MustPlanet(R0_Planet_0_num).Colonists = 0 @@ -162,7 +162,7 @@ func TestProduceShips(t *testing.T) { assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 1) assert.Equal(t, uint(106), c.ShipGroup(0).Number) progress = *c.MustPlanet(R0_Planet_0_num).Production.Progress - assert.InDelta(t, 0.0099, progress, 0.00001) // 1.(0099) drones with no CAP on planet + assert.InDelta(t, 0.0099, progress.F(), 0.00001) // 1.(0099) drones with no CAP on planet } func TestProduceShip(t *testing.T) { @@ -186,26 +186,26 @@ func TestProduceShip(t *testing.T) { r := controller.ProduceShip(&p, p.ProductionCapacity(), Drone.EmptyMass()) assert.Equal(t, uint(99), r) - assert.InDelta(t, 0.0099, *p.Production.Progress, 0.000001) + assert.InDelta(t, 0.0099, (*p.Production.Progress).F(), 0.000001) (&p).Production = game.ProductionShip.AsType(uuid.Nil) (&p).Capital = 10. 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., (*p.Production.Progress).F()) 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, p.ProductionCapacity(), BattleShip.EmptyMass()) assert.Equal(t, uint(1), r) - assert.InDelta(t, 0.1, *p.Production.Progress, 0.001) + assert.InDelta(t, 0.1, (*p.Production.Progress).F(), 0.001) (&p).Production = game.ProductionShip.AsType(uuid.Nil) (&p).Capital = 20. r = controller.ProduceShip(&p, p.ProductionCapacity(), BattleShip.EmptyMass()) assert.Equal(t, uint(1), r) - assert.Equal(t, 1./9., *p.Production.Progress) + assert.Equal(t, 1./9., (*p.Production.Progress).F()) // TODO: test with insufficient production capacity } @@ -213,6 +213,10 @@ func TestProduceShip(t *testing.T) { func TestListProducingPlanets(t *testing.T) { c, g := newCache() + c.MustPlanet(0).Production = game.ProductionNone.AsType(uuid.Nil) + c.MustPlanet(1).Production = game.ProductionNone.AsType(uuid.Nil) + c.MustPlanet(2).Production = game.ProductionNone.AsType(uuid.Nil) + planets := slices.Collect(c.ListProducingPlanets()) assert.Len(t, planets, 0) diff --git a/internal/controller/report.go b/internal/controller/report.go index 7df8d17..2dcdba0 100644 --- a/internal/controller/report.go +++ b/internal/controller/report.go @@ -73,7 +73,7 @@ func (c *Cache) InitReport(t uint) *mr.Report { 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 + sumVote[vi] += r.Votes.F() dest := &report.Player[vi] dest.Votes = mr.F(sumVote[vi]) } @@ -110,7 +110,7 @@ func (c *Cache) ReportRace(ri int, rep *mr.Report, battles []*mr.BattleReport, b // votes based on population // TODO: check vote values was previously calculated - rep.Votes = mr.F(r.Votes) + rep.Votes = mr.F(r.Votes.F()) // relations for i := range r.Relations { @@ -185,10 +185,10 @@ func (c *Cache) ReportLocalScience(ri int, rep *mr.Report) { 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) + rep.LocalScience[i].Drive = mr.F(r.Sciences[i].Drive.F()) + rep.LocalScience[i].Weapons = mr.F(r.Sciences[i].Weapons.F()) + rep.LocalScience[i].Shields = mr.F(r.Sciences[i].Shields.F()) + rep.LocalScience[i].Cargo = mr.F(r.Sciences[i].Cargo.F()) } slices.SortFunc(rep.LocalScience, func(a, b mr.Science) int { return cmp.Compare(a.Name, b.Name) }) @@ -215,10 +215,10 @@ func (c *Cache) ReportOtherScience(ri int, rep *mr.Report) { 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) + rep.OtherScience[i].Drive = mr.F(sc.Drive.F()) + rep.OtherScience[i].Weapons = mr.F(sc.Weapons.F()) + rep.OtherScience[i].Shields = mr.F(sc.Shields.F()) + rep.OtherScience[i].Cargo = mr.F(sc.Cargo.F()) i++ } @@ -236,11 +236,11 @@ func (c *Cache) ReportLocalShipClass(ri int, report *mr.Report) { 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].Drive = mr.F(st.Drive.F()) 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].Weapons = mr.F(st.Weapons.F()) + report.LocalShipClass[i].Shields = mr.F(st.Shields.F()) + report.LocalShipClass[i].Cargo = mr.F(st.Cargo.F()) report.LocalShipClass[i].Mass = mr.F(st.EmptyMass()) i++ } @@ -308,11 +308,11 @@ func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report) { 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].Drive = mr.F(st.Drive.F()) 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].Weapons = mr.F(st.Weapons.F()) + rep.OtherShipClass[i].Shields = mr.F(st.Shields.F()) + rep.OtherShipClass[i].Cargo = mr.F(st.Cargo.F()) rep.OtherShipClass[i].Mass = mr.F(st.EmptyMass()) i++ } @@ -543,7 +543,7 @@ func (c *Cache) ReportShipProduction(ri int, rep *mr.Report) { 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) + rep.ShipProduction[pi].Wasted = mr.F(free * (*p.Production.Progress).F()) i++ } } @@ -717,7 +717,7 @@ func (c *Cache) otherGroup(v *mr.OtherGroup, sg *game.ShipGroup, st *game.ShipTy 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.Tech[t.String()] = mr.F(val.F()) } v.Cargo = sg.CargoString() v.Load = mr.F(sg.Load.F()) diff --git a/internal/controller/report_test.go b/internal/controller/report_test.go index 1c9c099..cb0f91a 100644 --- a/internal/controller/report_test.go +++ b/internal/controller/report_test.go @@ -21,11 +21,11 @@ func TestReportLocalShipClass(t *testing.T) { 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, report.F(Cruiser.Drive.F()), 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) + assert.Equal(t, report.F(Cruiser.Weapons.F()), r.LocalShipClass[i].Weapons) + assert.Equal(t, report.F(Cruiser.Shields.F()), r.LocalShipClass[i].Shields) + assert.Equal(t, report.F(Cruiser.Cargo.F()), 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) diff --git a/internal/controller/science.go b/internal/controller/science.go index e93cfcd..bf4be47 100644 --- a/internal/controller/science.go +++ b/internal/controller/science.go @@ -46,10 +46,10 @@ func (c *Cache) CreateScience(ri int, name string, drive, weapons, shileds, carg c.g.Race[ri].Sciences = append(c.g.Race[ri].Sciences, game.Science{ ID: uuid.New(), Name: n, - Drive: drive, - Weapons: weapons, - Shields: shileds, - Cargo: cargo, + Drive: game.Float(drive), + Weapons: game.Float(weapons), + Shields: game.Float(shileds), + Cargo: game.Float(cargo), }) return nil } diff --git a/internal/controller/science_test.go b/internal/controller/science_test.go index 8ffcf48..1fd0982 100644 --- a/internal/controller/science_test.go +++ b/internal/controller/science_test.go @@ -22,10 +22,10 @@ func TestCreateScience(t *testing.T) { sc := c.RaceScience(Race_0_idx)[0] assert.NoError(t, uuid.Validate(sc.ID.String())) assert.Equal(t, first, sc.Name) - assert.Equal(t, 0.4, sc.Drive) - assert.Equal(t, 0., sc.Weapons) - assert.Equal(t, 0.6, sc.Shields) - assert.Equal(t, 0., sc.Cargo) + assert.Equal(t, 0.4, sc.Drive.F()) + assert.Equal(t, 0., sc.Weapons.F()) + assert.Equal(t, 0.6, sc.Shields.F()) + assert.Equal(t, 0., sc.Cargo.F()) assert.ErrorContains(t, g.CreateScience("UnknownRace", second, 0.4, 0, 0.6, 0), @@ -66,10 +66,10 @@ func TestCreateScience(t *testing.T) { sc = c.RaceScience(Race_0_idx)[1] assert.NoError(t, uuid.Validate(sc.ID.String())) assert.Equal(t, second, sc.Name) - assert.Equal(t, 0.25, sc.Drive) - assert.Equal(t, 0.25, sc.Weapons) - assert.Equal(t, 0.25, sc.Shields) - assert.Equal(t, 0.25, sc.Cargo) + assert.Equal(t, 0.25, sc.Drive.F()) + assert.Equal(t, 0.25, sc.Weapons.F()) + assert.Equal(t, 0.25, sc.Shields.F()) + assert.Equal(t, 0.25, sc.Cargo.F()) } func TestDeleteScience(t *testing.T) { diff --git a/internal/controller/ship_class.go b/internal/controller/ship_class.go index 0c9da55..bf3fa02 100644 --- a/internal/controller/ship_class.go +++ b/internal/controller/ship_class.go @@ -33,11 +33,11 @@ func (c *Cache) CreateShipType(ri int, typeName string, drive float64, ammo int, c.g.Race[ri].ShipTypes = append(c.g.Race[ri].ShipTypes, game.ShipType{ ID: uuid.New(), Name: n, - Drive: drive, + Drive: game.Float(drive), Armament: uint(ammo), - Weapons: weapons, - Shields: shileds, - Cargo: cargo, + Weapons: game.Float(weapons), + Shields: game.Float(shileds), + Cargo: game.Float(cargo), }) c.invalidateShipGroupCache() c.invalidateFleetCache() diff --git a/internal/controller/ship_group.go b/internal/controller/ship_group.go index 5d50b8e..e712bab 100644 --- a/internal/controller/ship_group.go +++ b/internal/controller/ship_group.go @@ -36,11 +36,11 @@ func (c *Cache) createShipsUnsafe(ri int, classID uuid.UUID, planet uint, quanti TypeID: classID, Destination: planet, Number: uint(quantity), - Tech: map[game.Tech]float64{ - game.TechDrive: c.g.Race[ri].TechLevel(game.TechDrive), - game.TechWeapons: c.g.Race[ri].TechLevel(game.TechWeapons), - game.TechShields: c.g.Race[ri].TechLevel(game.TechShields), - game.TechCargo: c.g.Race[ri].TechLevel(game.TechCargo), + Tech: map[game.Tech]game.Float{ + game.TechDrive: game.F(c.g.Race[ri].TechLevel(game.TechDrive)), + game.TechWeapons: game.F(c.g.Race[ri].TechLevel(game.TechWeapons)), + game.TechShields: game.F(c.g.Race[ri].TechLevel(game.TechShields)), + game.TechCargo: game.F(c.g.Race[ri].TechLevel(game.TechCargo)), }, }) @@ -459,11 +459,11 @@ func (c *Cache) GiveawayGroup(ri, riAccept int, groupIndex, quantity uint) (err if stAcc < 0 { err = c.CreateShipType(riAccept, st.Name, - st.Drive, + st.Drive.F(), int(st.Armament), - st.Weapons, - st.Shields, - st.Cargo) + st.Weapons.F(), + st.Shields.F(), + st.Cargo.F()) if err != nil { return err } diff --git a/internal/controller/ship_group_send_test.go b/internal/controller/ship_group_send_test.go index 586f147..aa97191 100644 --- a/internal/controller/ship_group_send_test.go +++ b/internal/controller/ship_group_send_test.go @@ -51,8 +51,8 @@ func TestSendGroup(t *testing.T) { assert.Equal(t, uint(3), c.ShipGroup(3).Number) assert.Equal(t, game.StateLaunched, c.ShipGroup(3).State()) assert.NotNil(t, c.ShipGroup(3).StateInSpace) - assert.Equal(t, c.MustPlanet(R0_Planet_0_num).X.F(), c.ShipGroup(3).StateInSpace.X) - assert.Equal(t, c.MustPlanet(R0_Planet_0_num).Y.F(), c.ShipGroup(3).StateInSpace.Y) + assert.Equal(t, c.MustPlanet(R0_Planet_0_num).X, c.ShipGroup(3).StateInSpace.X) + assert.Equal(t, c.MustPlanet(R0_Planet_0_num).Y, c.ShipGroup(3).StateInSpace.Y) assert.NoError(t, g.SendGroup(Race_0.Name, 4, R0_Planet_0_num, 2)) // un-send 2 of 3 assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 4) @@ -61,8 +61,8 @@ func TestSendGroup(t *testing.T) { assert.Equal(t, uint(1), c.MustShipGroup(Race_0_idx, 4).Number) assert.Equal(t, game.StateLaunched, c.MustShipGroup(Race_0_idx, 4).State()) assert.NotNil(t, c.MustShipGroup(Race_0_idx, 4).StateInSpace) - assert.Equal(t, c.MustPlanet(R0_Planet_0_num).X.F(), c.MustShipGroup(Race_0_idx, 4).StateInSpace.X) - assert.Equal(t, c.MustPlanet(R0_Planet_0_num).Y.F(), c.MustShipGroup(Race_0_idx, 4).StateInSpace.Y) + assert.Equal(t, c.MustPlanet(R0_Planet_0_num).X, c.MustShipGroup(Race_0_idx, 4).StateInSpace.X) + assert.Equal(t, c.MustPlanet(R0_Planet_0_num).Y, c.MustShipGroup(Race_0_idx, 4).StateInSpace.Y) assert.NoError(t, g.SendGroup(Race_0.Name, 4, R0_Planet_0_num, 0)) // un-send the rest 1 assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 3) diff --git a/internal/controller/ship_group_test.go b/internal/controller/ship_group_test.go index a389207..4365b10 100644 --- a/internal/controller/ship_group_test.go +++ b/internal/controller/ship_group_test.go @@ -52,7 +52,7 @@ func TestJoinEqualGroups(t *testing.T) { assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 7) c.RaceTechLevel(Race_1_idx, game.TechShields, 2.0) - assert.Equal(t, 2.0, c.Race(Race_1_idx).Tech[game.TechShields]) + assert.Equal(t, 2.0, c.Race(Race_1_idx).Tech[game.TechShields].F()) assert.NoError(t, c.CreateShips(1, Race_1_Freighter, R1_Planet_1_num, 1)) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 3) diff --git a/internal/controller/ship_group_upgrade.go b/internal/controller/ship_group_upgrade.go index bf39628..472a10c 100644 --- a/internal/controller/ship_group_upgrade.go +++ b/internal/controller/ship_group_upgrade.go @@ -1,6 +1,9 @@ package controller import ( + "math" + "slices" + "github.com/google/uuid" e "github.com/iliadenisov/galaxy/internal/error" "github.com/iliadenisov/galaxy/internal/model/game" @@ -66,19 +69,15 @@ 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), sg.TechLevel(tech).F(), limitLevel) + targetLevel[tech] = FutureUpgradeLevel(c.g.Race[ri].TechLevel(tech), sg.TechLevel(tech).F(), limitLevel) } else { - targetLevel[tech] = game.CurrentUpgradingLevel(sg, tech) + targetLevel[tech] = CurrentUpgradingLevel(sg, tech) } sumLevels += targetLevel[tech] } 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]) + uc := 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) @@ -118,7 +117,7 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi } // sanity check - uc = game.GroupUpgradeCost(sg, *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo]) + uc = 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) @@ -126,9 +125,6 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi // break group if needed 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 { return err @@ -149,5 +145,91 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi func (c *Cache) UpgradeShipGroup(sgi int, tech game.Tech, v float64) { sg := *(c.ShipGroup(sgi)) st := c.ShipGroupShipClass(sgi) - c.g.ShipGroups[sgi] = game.UpgradeGroupPreference(sg, *st, tech, v) + c.g.ShipGroups[sgi] = UpgradeGroupPreference(sg, *st, tech, v) +} + +// helpers + +type UpgradeCalc struct { + Cost map[game.Tech]float64 +} + +func (uc UpgradeCalc) UpgradeCost(ships uint) float64 { + var sum float64 + for _, v := range uc.Cost { + sum += v + } + return sum * float64(ships) +} + +func (uc UpgradeCalc) UpgradeMaxShips(resources float64) uint { + return uint(math.Floor(resources / uc.UpgradeCost(1))) +} + +func BlockUpgradeCost(blockMass, currentBlockTech, targetBlockTech float64) float64 { + if blockMass == 0 || targetBlockTech <= currentBlockTech { + return 0 + } + return (1 - currentBlockTech/targetBlockTech) * 10 * blockMass +} + +func GroupUpgradeCost(sg *game.ShipGroup, st game.ShipType, drive, weapons, shields, cargo float64) UpgradeCalc { + uc := &UpgradeCalc{Cost: make(map[game.Tech]float64)} + if drive > 0 { + uc.Cost[game.TechDrive] = BlockUpgradeCost(st.DriveBlockMass(), sg.TechLevel(game.TechDrive).F(), drive) + } + if weapons > 0 { + uc.Cost[game.TechWeapons] = BlockUpgradeCost(st.WeaponsBlockMass(), sg.TechLevel(game.TechWeapons).F(), weapons) + } + if shields > 0 { + uc.Cost[game.TechShields] = BlockUpgradeCost(st.ShieldsBlockMass(), sg.TechLevel(game.TechShields).F(), shields) + } + if cargo > 0 { + uc.Cost[game.TechCargo] = BlockUpgradeCost(st.CargoBlockMass(), sg.TechLevel(game.TechCargo).F(), cargo) + } + return *uc +} + +func CurrentUpgradingLevel(sg *game.ShipGroup, tech game.Tech) float64 { + if sg.StateUpgrade == nil { + return 0 + } + ti := slices.IndexFunc(sg.StateUpgrade.UpgradeTech, func(pref game.UpgradePreference) bool { return pref.Tech == tech }) + if ti >= 0 { + return sg.StateUpgrade.UpgradeTech[ti].Level.F() + } + return 0 +} + +func FutureUpgradeLevel(raceLevel, groupLevel, limit float64) float64 { + target := limit + if target == 0 || target > raceLevel { + target = raceLevel + } + if groupLevel == target { + return 0 + } + return target +} + +func UpgradeGroupPreference(sg game.ShipGroup, st game.ShipType, tech game.Tech, v float64) game.ShipGroup { + if v <= 0 || st.BlockMass(tech) == 0 || sg.TechLevel(tech).F() >= v { + return sg + } + var su game.InUpgrade + if sg.StateUpgrade != nil { + su = *sg.StateUpgrade + } else { + su = game.InUpgrade{UpgradeTech: []game.UpgradePreference{}} + } + ti := slices.IndexFunc(su.UpgradeTech, func(pref game.UpgradePreference) bool { return pref.Tech == tech }) + if ti < 0 { + su.UpgradeTech = append(su.UpgradeTech, game.UpgradePreference{Tech: tech}) + ti = len(su.UpgradeTech) - 1 + } + su.UpgradeTech[ti].Level = game.F(v) + su.UpgradeTech[ti].Cost = game.F(BlockUpgradeCost(st.BlockMass(tech), sg.TechLevel(tech).F(), v) * float64(sg.Number)) + + sg.StateUpgrade = &su + return sg } diff --git a/internal/controller/ship_group_upgrade_test.go b/internal/controller/ship_group_upgrade_test.go index 7510096..f4b941a 100644 --- a/internal/controller/ship_group_upgrade_test.go +++ b/internal/controller/ship_group_upgrade_test.go @@ -4,11 +4,126 @@ import ( "slices" "testing" + "github.com/iliadenisov/galaxy/internal/controller" e "github.com/iliadenisov/galaxy/internal/error" "github.com/iliadenisov/galaxy/internal/model/game" + g "github.com/iliadenisov/galaxy/internal/model/game" "github.com/stretchr/testify/assert" ) +// var ( +// Cruiser = game.ShipType{ +// Name: "Cruiser", +// Drive: 15, +// Armament: 1, +// Weapons: 15, +// Shields: 15, +// Cargo: 0, +// } +// ) + +func TestBlockUpgradeCost(t *testing.T) { + assert.Equal(t, 00.0, controller.BlockUpgradeCost(1, 1.0, 1.0)) + assert.Equal(t, 25.0, controller.BlockUpgradeCost(5, 1.0, 2.0)) + assert.Equal(t, 50.0, controller.BlockUpgradeCost(10, 1.0, 2.0)) +} + +func TestGroupUpgradeCost(t *testing.T) { + sg := &g.ShipGroup{ + Tech: map[g.Tech]g.Float{ + g.TechDrive: 1.0, + g.TechWeapons: 1.0, + g.TechShields: 1.0, + g.TechCargo: 1.0, + }, + Number: 1, + } + assert.Equal(t, 225.0, controller.GroupUpgradeCost(sg, Cruiser, 2.0, 2.0, 2.0, 2.0).UpgradeCost(1)) +} + +func TestUpgradeMaxShips(t *testing.T) { + sg := &g.ShipGroup{ + Tech: map[g.Tech]g.Float{ + g.TechDrive: 1.0, + g.TechWeapons: 1.0, + g.TechShields: 1.0, + g.TechCargo: 1.0, + }, + Number: 10, + } + uc := controller.GroupUpgradeCost(sg, Cruiser, 2.0, 2.0, 2.0, 2.0) + assert.Equal(t, uint(4), uc.UpgradeMaxShips(1000)) +} + +func TestCurrentUpgradingLevel(t *testing.T) { + sg := &g.ShipGroup{ + StateUpgrade: nil, + } + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechDrive)) + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechWeapons)) + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechShields)) + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechCargo)) + + sg.StateUpgrade = &g.InUpgrade{ + UpgradeTech: []g.UpgradePreference{ + {Tech: g.TechDrive, Level: 1.5, Cost: 100.1}, + }, + } + assert.Equal(t, 1.5, controller.CurrentUpgradingLevel(sg, g.TechDrive)) + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechWeapons)) + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechShields)) + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechCargo)) + + sg.StateUpgrade.UpgradeTech = append(sg.StateUpgrade.UpgradeTech, g.UpgradePreference{Tech: g.TechCargo, Level: 2.2, Cost: 200.2}) + assert.Equal(t, 1.5, controller.CurrentUpgradingLevel(sg, g.TechDrive)) + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechWeapons)) + assert.Equal(t, 0.0, controller.CurrentUpgradingLevel(sg, g.TechShields)) + assert.Equal(t, 2.2, controller.CurrentUpgradingLevel(sg, g.TechCargo)) +} + +func TestFutureUpgradeLevel(t *testing.T) { + assert.Equal(t, 0.0, controller.FutureUpgradeLevel(2.0, 2.0, 2.0)) + assert.Equal(t, 0.0, controller.FutureUpgradeLevel(2.0, 2.0, 3.0)) + assert.Equal(t, 1.5, controller.FutureUpgradeLevel(1.5, 2.0, 3.0)) + assert.Equal(t, 2.0, controller.FutureUpgradeLevel(2.5, 1.0, 2.0)) + assert.Equal(t, 2.5, controller.FutureUpgradeLevel(2.5, 1.0, 0.0)) +} + +func TestUpgradeGroupPreference(t *testing.T) { + sg := g.ShipGroup{ + Number: 4, + Tech: g.TechSet{ + g.TechDrive: 1.0, + g.TechWeapons: 1.0, + g.TechShields: 1.0, + g.TechCargo: 1.0, + }, + } + assert.Nil(t, sg.StateUpgrade) + sg = controller.UpgradeGroupPreference(sg, Cruiser, g.TechDrive, 0) + assert.Nil(t, sg.StateUpgrade) + + sg = controller.UpgradeGroupPreference(sg, Cruiser, g.TechDrive, 2.0) + assert.NotNil(t, sg.StateUpgrade) + assert.Equal(t, 300., sg.StateUpgrade.TechCost(g.TechDrive)) + assert.Equal(t, 300., sg.StateUpgrade.Cost()) + + sg = controller.UpgradeGroupPreference(sg, Cruiser, g.TechWeapons, 2.0) + assert.NotNil(t, sg.StateUpgrade) + assert.Equal(t, 300., sg.StateUpgrade.TechCost(g.TechWeapons)) + assert.Equal(t, 600., sg.StateUpgrade.Cost()) + + sg = controller.UpgradeGroupPreference(sg, Cruiser, g.TechShields, 2.0) + assert.NotNil(t, sg.StateUpgrade) + assert.Equal(t, 300., sg.StateUpgrade.TechCost(g.TechShields)) + assert.Equal(t, 900., sg.StateUpgrade.Cost()) + + sg = controller.UpgradeGroupPreference(sg, Cruiser, g.TechCargo, 2.0) + assert.NotNil(t, sg.StateUpgrade) + assert.Equal(t, 0., sg.StateUpgrade.TechCost(g.TechCargo)) + assert.Equal(t, 900., sg.StateUpgrade.Cost()) +} + func TestUpgradeGroup(t *testing.T) { c, g := newCache() // group #1 - in_orbit, free to upgrade @@ -63,5 +178,5 @@ func TestUpgradeGroup(t *testing.T) { assert.ErrorContains(t, g.UpgradeGroup(Race_0.Name, 4, "DRIVE", 1, 1.3), - e.GenericErrorText(e.ErrInputUpgradeGroupBreakNotAllowed)) + e.GenericErrorText(e.ErrShipsBusy)) } diff --git a/internal/controller/vote.go b/internal/controller/vote.go index a6ef1be..2889fc3 100644 --- a/internal/controller/vote.go +++ b/internal/controller/vote.go @@ -48,11 +48,12 @@ func (c *Cache) TurnCalculateVotes() []int { c.g.Votes = 0 for ri, votes := range raceVotes { - c.g.Race[ri].Votes = votes - c.g.Votes += votes + v := game.F(votes) + c.g.Race[ri].Votes = v + c.g.Votes += v } - return votingWinners(calc, c.g.Votes) + return votingWinners(calc, c.g.Votes.F()) } func VotingGraph(races []game.Race, raceIndex func(uuid.UUID) int) []*VoteNode { diff --git a/internal/error/generic.go b/internal/error/generic.go index 12c4083..10057ec 100644 --- a/internal/error/generic.go +++ b/internal/error/generic.go @@ -61,7 +61,6 @@ const ( ErrInputUpgradeShipTechNotUsed ErrInputUpgradeParameterNotAllowed ErrInputUpgradeShipsAlreadyUpToDate - ErrInputUpgradeGroupBreakNotAllowed ErrInputUpgradeTechLevelInsufficient ) @@ -161,8 +160,6 @@ func GenericErrorText(code int) string { return "Not enough ships in the group to make an upgrade" case ErrUpgradeInsufficientResources: return "Insufficient planet production capacity" - case ErrInputUpgradeGroupBreakNotAllowed: - return "The Group is already in upgrade state and can't be divided to a smaller group" case ErrInputUpgradeTechLevelInsufficient: return "Insifficient Tech level for requested upgrade" case ErrSendShipHasNoDrives: diff --git a/internal/error/input.go b/internal/error/input.go index b889323..cc3d1c1 100644 --- a/internal/error/input.go +++ b/internal/error/input.go @@ -160,10 +160,6 @@ func NewUpgradeInsufficientResourcesError(arg ...any) error { return newGenericError(ErrUpgradeInsufficientResources, arg...) } -func NewUpgradeGroupBreakNotAllowedError(arg ...any) error { - return newGenericError(ErrInputUpgradeGroupBreakNotAllowed, arg...) -} - func NewUpgradeTechLevelInsufficientError(arg ...any) error { return newGenericError(ErrInputUpgradeTechLevelInsufficient, arg...) } diff --git a/internal/game/cmd_ship_type_test.go b/internal/game/cmd_ship_type_test.go index 7f329b2..524eb31 100644 --- a/internal/game/cmd_ship_type_test.go +++ b/internal/game/cmd_ship_type_test.go @@ -26,10 +26,10 @@ func TestCreateShipType(t *testing.T) { assert.NoError(t, err) assert.Len(t, st, 1) assert.Equal(t, st[0].Name, typeName) - assert.Equal(t, st[0].Drive, 1.) - assert.Equal(t, st[0].Weapons, 0.) - assert.Equal(t, st[0].Shields, 0.) - assert.Equal(t, st[0].Cargo, 0.) + assert.Equal(t, st[0].Drive.F(), 1.) + assert.Equal(t, st[0].Weapons.F(), 0.) + assert.Equal(t, st[0].Shields.F(), 0.) + assert.Equal(t, st[0].Cargo.F(), 0.) assert.Equal(t, st[0].Armament, uint(0)) // TODO: test with existing ship group err = game.DeleteShipType(p, unknownRaceName, typeName) // TODO: test on dead race diff --git a/internal/game/controller_test.go b/internal/game/controller_test.go index 103e098..573e013 100644 --- a/internal/game/controller_test.go +++ b/internal/game/controller_test.go @@ -25,7 +25,7 @@ func TestComposeGame(t *testing.T) { g(t, func(p func(*controller.Param), g func() *mg.Game) { _, err := game.GenerateGame(p, []string{"r1", "r2"}) assert.Error(t, err) - assert.ErrorContains(t, err, "state for turn 0 already saved") + assert.ErrorContains(t, err, "turn 0 already saved at 0000/state.json") }) } diff --git a/internal/model/game/fleet_send.go b/internal/model/game/fleet_send.go deleted file mode 100644 index c7aae5a..0000000 --- a/internal/model/game/fleet_send.go +++ /dev/null @@ -1,80 +0,0 @@ -package game - -// import ( -// "fmt" -// "slices" - -// e "github.com/iliadenisov/galaxy/internal/error" -// "github.com/iliadenisov/galaxy/internal/util" -// ) - -// func (g *Game) SendFleet(raceName, fleetName string, planetNumber uint) error { -// ri, err := g.raceIndex(raceName) -// if err != nil { -// return err -// } -// fi := g.fleetIndex(ri, fleetName) -// if fi < 0 { -// return e.NewEntityNotExistsError("fleet %q", fleetName) -// } -// return g.sendFleetInternal(ri, fi, planetNumber) -// } - -// func (g *Game) sendFleetInternal(ri, fi int, planetNumber uint) error { -// state, sourcePlanet, _ := FleetState(g, g.Fleets[fi].ID) -// if StateInOrbit != state && StateLaunched != state { -// return e.NewShipsBusyError() -// } - -// p1, ok := PlanetByNum(g, *sourcePlanet) -// if !ok { -// return e.NewGameStateError("source planet #%d does not exists", sourcePlanet) -// } -// p2, ok := PlanetByNum(g, planetNumber) -// if !ok { -// return e.NewEntityNotExistsError("destination planet #%d", planetNumber) -// } -// rangeToDestination := util.ShortDistance(g.Map.Width, g.Map.Height, p1.X, p1.Y, p2.X, p2.Y) -// if rangeToDestination > g.Race[ri].FlightDistance() { -// return e.NewSendUnreachableDestinationError("range=%.03f", rangeToDestination) -// } - -// for sg := range FleetGroups(g, ri, fi) { -// st, ok := ShipClass(g, ri, sg.TypeID) -// if !ok { -// return e.NewGameStateError("not found: ShipType ID=%v", sg.TypeID) -// } -// if st.DriveBlockMass() == 0 { -// return e.NewSendShipHasNoDrivesError("Class=%s", st.Name) -// } -// } - -// if *sourcePlanet == planetNumber { -// UnsendFleet(g, ri, fi) -// return nil -// } - -// LaunchFleet(g, ri, fi, planetNumber) - -// return nil -// } - -// func LaunchFleet(g *Game, ri, fi int, destination uint) { -// for sg := range FleetGroups(g, ri, fi) { -// sgi := slices.IndexFunc(g.ShipGroups, func(s ShipGroup) bool { return sg.Index == s.Index }) -// if sgi < 0 { -// panic(fmt.Sprintf("LauncgFleet: cannot find ship group index=%d", sg.Index)) -// } -// g.ShipGroups[sgi] = LaunchShips(sg, destination) -// } -// } - -// func UnsendFleet(g *Game, ri, fi int) { -// for sg := range FleetGroups(g, ri, fi) { -// sgi := slices.IndexFunc(g.ShipGroups, func(s ShipGroup) bool { return sg.Index == s.Index }) -// if sgi < 0 { -// panic(fmt.Sprintf("UnsendFleet: cannot find ship group index=%d", sg.Index)) -// } -// g.ShipGroups[sgi] = UnsendShips(sg) -// } -// } diff --git a/internal/model/game/fleet_send_test.go b/internal/model/game/fleet_send_test.go deleted file mode 100644 index f4c3f3b..0000000 --- a/internal/model/game/fleet_send_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package game_test - -// import ( -// "slices" -// "testing" - -// e "github.com/iliadenisov/galaxy/internal/error" -// "github.com/iliadenisov/galaxy/internal/model/game" -// "github.com/stretchr/testify/assert" -// ) - -// func TestSendFleet(t *testing.T) { -// g := newGame() -// // group #1 - in_orbit Planet_0 -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) -// // group #2 - in_space (later) -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) -// // group #3 - in_orbit Planet_0, unmovable -// g.CreateShipType(Race_0.Name, "Fortress", 0, 50, 30, 100, 0) -// assert.NoError(t, g.CreateShips(Race_0_idx, "Fortress", R0_Planet_0_num, 1)) -// // group #4 - in_orbit Planet_0 -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2)) - -// // ensure race has no Fleets -// assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0) - -// fleetSending := "R0_Fleet_one" -// fleetInSpace := "R0_Fleet_inSpace" -// fleetUnmovable := "R0_Fleet_unmovable" - -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 1, 0)) -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSending, 3, 0)) - -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetInSpace, 2, 0)) -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetUnmovable, 3, 0)) -// // group #2 - in_space -// g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23} - -// assert.ErrorContains(t, -// g.SendFleet("UnknownRace", fleetSending, 2), -// e.GenericErrorText(e.ErrInputUnknownRace)) -// assert.ErrorContains(t, -// g.SendFleet(Race_0.Name, "UnknownFleet", 2), -// e.GenericErrorText(e.ErrInputEntityNotExists)) -// assert.ErrorContains(t, -// g.SendFleet(Race_0.Name, fleetInSpace, 2), -// e.GenericErrorText(e.ErrShipsBusy)) -// assert.ErrorContains(t, -// g.SendFleet(Race_0.Name, fleetSending, 200), -// e.GenericErrorText(e.ErrInputEntityNotExists)) -// assert.ErrorContains(t, -// g.SendFleet(Race_0.Name, fleetSending, 3), -// e.GenericErrorText(e.ErrSendUnreachableDestination)) -// assert.ErrorContains(t, -// g.SendFleet(Race_0.Name, fleetUnmovable, 2), -// e.GenericErrorText(e.ErrSendShipHasNoDrives)) - -// assert.NoError(t, g.SendFleet(Race_0.Name, fleetSending, 2)) -// fi := slices.IndexFunc(slices.Collect(g.ListFleets(Race_0_idx)), func(f game.Fleet) bool { return f.Name == fleetSending }) -// state, _, _ := game.FleetState(g, g.Fleets[fi].ID) -// assert.Equal(t, game.StateLaunched, state) -// for sg := range game.FleetGroups(g, Race_0_idx, fi) { -// assert.Equal(t, game.StateLaunched, sg.State()) -// } - -// assert.NoError(t, g.SendFleet(Race_0.Name, fleetSending, 0)) -// fi = slices.IndexFunc(slices.Collect(g.ListFleets(Race_0_idx)), func(f game.Fleet) bool { return f.Name == fleetSending }) -// state, _, _ = game.FleetState(g, g.Fleets[fi].ID) -// assert.Equal(t, game.StateInOrbit, state) -// for sg := range game.FleetGroups(g, Race_0_idx, fi) { -// assert.Equal(t, game.StateInOrbit, sg.State()) -// } -// } diff --git a/internal/model/game/fleet_test.go b/internal/model/game/fleet_test.go deleted file mode 100644 index e552c31..0000000 --- a/internal/model/game/fleet_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package game_test - -// import ( -// "slices" -// "testing" - -// e "github.com/iliadenisov/galaxy/internal/error" -// "github.com/iliadenisov/galaxy/internal/model/game" -// "github.com/stretchr/testify/assert" -// ) - -// func TestJoinShipGroupToFleet(t *testing.T) { -// g := newGame() -// var groupIndex uint = 1 - -// assert.ErrorContains(t, -// g.JoinShipGroupToFleet(Race_0.Name, " ", groupIndex, 0), -// e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)) - -// assert.ErrorContains(t, -// g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 0), -// e.GenericErrorText(e.ErrInputEntityNotExists)) - -// // creating ShipGroup -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 5)) - -// assert.ErrorContains(t, -// g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 6), -// e.GenericErrorText(e.ErrJoinFleetGroupNumberNotEnough)) - -// // ensure race has no Fleets -// assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0) - -// fleetOne := "R0_Fleet_one" -// fleetTwo := "R0_Fleet_two" - -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0)) -// fleets := slices.Collect(g.ListFleets(Race_0_idx)) -// groups := slices.Collect(g.ListShipGroups(Race_0_idx)) -// assert.Len(t, groups, 1) -// gi := 0 -// assert.Len(t, fleets, 1) -// assert.Equal(t, fleets[0].Name, fleetOne) -// state, _, _ := game.FleetState(g, fleets[0].ID) -// assert.Equal(t, game.StateInOrbit, state) - -// assert.NotNil(t, groups[gi].FleetID) -// assert.Equal(t, fleets[0].ID, *groups[gi].FleetID) - -// // create another ShipGroup -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) -// groupIndex = 2 -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTwo, groupIndex, 2)) -// fleets = slices.Collect(g.ListFleets(Race_0_idx)) -// groups = slices.Collect(g.ListShipGroups(Race_0_idx)) -// assert.Len(t, groups, 3) -// gi = 1 -// assert.Len(t, fleets, 2) -// assert.Equal(t, fleets[1].Name, fleetTwo) -// state, _, _ = game.FleetState(g, fleets[1].ID) -// assert.Equal(t, game.StateInOrbit, state) - -// assert.NotNil(t, groups[gi].FleetID) -// assert.Equal(t, fleets[1].ID, *groups[gi].FleetID) -// assert.Equal(t, uint(2), groups[gi].Number) -// assert.Equal(t, uint(2), groups[gi].Index) - -// gi = 2 -// assert.Nil(t, groups[gi].FleetID) -// assert.Equal(t, uint(1), groups[gi].Number) -// assert.Equal(t, uint(3), groups[gi].Index) - -// groupIndex = groups[gi].Index -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0)) -// fleets = slices.Collect(g.ListFleets(Race_0_idx)) -// assert.Len(t, fleets, 2) -// groups = slices.Collect(g.ListShipGroups(Race_0_idx)) -// assert.NotNil(t, groups[gi].FleetID) -// assert.Equal(t, fleets[0].ID, *groups[gi].FleetID) -// state, _, _ = game.FleetState(g, fleets[0].ID) -// assert.Equal(t, game.StateInOrbit, state) - -// // group not In_Orbit -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7)) -// gi = 3 -// g.ShipGroups[gi].StateInSpace = &game.InSpace{ -// Origin: 2, -// Range: 1, -// } -// assert.ErrorContains(t, -// g.JoinShipGroupToFleet(Race_0.Name, fleetOne, g.ShipGroups[gi].Index, 0), -// e.GenericErrorText(e.ErrShipsBusy)) -// g.ShipGroups[gi].StateInSpace = nil - -// // existing fleet not on the same planet or in_orbit -// g.ShipGroups[0].StateInSpace = &game.InSpace{ -// Origin: 2, -// Range: 1, -// } -// g.ShipGroups[2].StateInSpace = g.ShipGroups[0].StateInSpace -// assert.ErrorContains(t, -// g.JoinShipGroupToFleet(Race_0.Name, fleetOne, g.ShipGroups[gi].Index, 0), -// e.GenericErrorText(e.ErrShipsNotOnSamePlanet)) -// } - -// func TestJoinFleets(t *testing.T) { -// g := newGame() -// // creating ShipGroup #1 at Planet_0 -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // group #1 -// // creating ShipGroup #2 at Planet_2 -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_2_num, 2)) // group #2 -// // creating ShipGroup #3 at Planet_0 -// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) // group #3 - -// // ensure race has no Fleets -// assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 0) - -// fleetPlanet2 := "R0_Fleet_On_Planet_2" -// fleetSource := "R0_Fleet_one" -// fleetTarget := "R0_Fleet_two" - -// assert.ErrorContains(t, -// g.JoinFleets(Race_0.Name, fleetSource, fleetTarget), -// e.GenericErrorText(e.ErrInputEntityNotExists)) -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSource, 1, 0)) -// assert.ErrorContains(t, -// g.JoinFleets(Race_0.Name, fleetSource, fleetTarget), -// e.GenericErrorText(e.ErrInputEntityNotExists)) -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTarget, 3, 0)) -// assert.NoError(t, g.JoinFleets(Race_0.Name, fleetSource, fleetTarget)) - -// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetPlanet2, 2, 0)) -// assert.ErrorContains(t, -// g.JoinFleets(Race_0.Name, fleetPlanet2, fleetTarget), -// e.GenericErrorText(e.ErrShipsNotOnSamePlanet)) -// } diff --git a/internal/model/game/game.go b/internal/model/game/game.go index fb7c242..9de28fe 100644 --- a/internal/model/game/game.go +++ b/internal/model/game/game.go @@ -22,11 +22,11 @@ func (f Float) F() float64 { return float64(f) } -type TechSet map[Tech]float64 +type TechSet map[Tech]Float func (ts TechSet) Value(t Tech) float64 { if v, ok := ts[t]; ok { - return v + return v.F() } else { panic(fmt.Sprintf("TechSet: Value: %s's value not set", t.String())) } @@ -34,7 +34,7 @@ func (ts TechSet) Value(t Tech) float64 { func (ts TechSet) Set(t Tech, v float64) TechSet { m := maps.Clone(ts) - m[t] = v + m[t] = F(v) return m } @@ -44,7 +44,7 @@ type Game struct { Turn uint `json:"turn"` Map Map `json:"map"` Race []Race `json:"races"` - Votes float64 `json:"votes"` + Votes Float `json:"votes"` ShipGroups []ShipGroup `json:"shipGroup,omitempty"` Fleets []Fleet `json:"fleet,omitempty"` Winner []uuid.UUID `json:"winner,omitempty"` diff --git a/internal/model/game/game_export_test.go b/internal/model/game/game_export_test.go deleted file mode 100644 index 13c7b64..0000000 --- a/internal/model/game/game_export_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package game - -// import "iter" - -// func (g *Game) CreateShips(ri int, shipTypeName string, planetNumber uint, quantity int) error { -// return nil // g.createShips(ri, shipTypeName, int(planetNumber), quantity) -// } - -// func (g Game) ListShipGroups(ri int) iter.Seq[ShipGroup] { -// return func(yield func(ShipGroup) bool) {} -// // return g.listShipGroups(ri) -// } - -// func (g Game) ListFleets(ri int) iter.Seq[Fleet] { -// return func(yield func(Fleet) bool) {} -// // return g.listFleets(ri) -// } - -// func (g Game) MustPlanetByNumber(num uint) Planet { -// p, err := g.PlanetByNumber(num) -// if err != nil { -// panic(err) -// } -// return p -// } diff --git a/internal/model/game/group.go b/internal/model/game/group.go index d1c2532..770049e 100644 --- a/internal/model/game/group.go +++ b/internal/model/game/group.go @@ -69,7 +69,7 @@ type InUpgrade struct { func (iu InUpgrade) Cost() float64 { var sum float64 for i := range iu.UpgradeTech { - sum += iu.UpgradeTech[i].Cost + sum += iu.UpgradeTech[i].Cost.F() } return sum } @@ -77,16 +77,16 @@ func (iu InUpgrade) Cost() float64 { func (iu InUpgrade) TechCost(t Tech) float64 { for i := range iu.UpgradeTech { if iu.UpgradeTech[i].Tech == t { - return iu.UpgradeTech[i].Cost + return iu.UpgradeTech[i].Cost.F() } } return 0. } type UpgradePreference struct { - Tech Tech `json:"tech"` - Level float64 `json:"level"` - Cost float64 `json:"cost"` + Tech Tech `json:"tech"` + Level Float `json:"level"` + Cost Float `json:"cost"` } type Tech string @@ -177,7 +177,7 @@ func (sg ShipGroup) Equal(other ShipGroup) bool { // Грузоподъёмность func (sg ShipGroup) CargoCapacity(st *ShipType) float64 { - return sg.TechLevel(TechCargo).F() * (st.Cargo + (st.Cargo*st.Cargo)/20) * float64(sg.Number) + return sg.TechLevel(TechCargo).F() * (st.Cargo.F() + (st.Cargo.F()*st.Cargo.F())/20) * float64(sg.Number) } // Масса перевозимого груза - @@ -200,7 +200,7 @@ func (sg ShipGroup) FullMass(st *ShipType) float64 { // Эффективность двигателя - // равна мощности Двигателей, умноженной на технологический уровень блока Двигателей func (sg ShipGroup) DriveEffective(st *ShipType) float64 { - return st.Drive * sg.TechLevel(TechDrive).F() + return st.Drive.F() * sg.TechLevel(TechDrive).F() } // Корабли перемещаются за один ход на количество световых лет, равное @@ -210,7 +210,7 @@ func (sg ShipGroup) Speed(st *ShipType) float64 { } func (sg ShipGroup) UpgradeDriveCost(st *ShipType, drive float64) float64 { - return (1 - sg.TechLevel(TechDrive).F()/drive) * 10 * st.Drive + return (1 - sg.TechLevel(TechDrive).F()/drive) * 10 * st.Drive.F() } // TODO: test on other values @@ -219,17 +219,17 @@ func (sg ShipGroup) UpgradeWeaponsCost(st *ShipType, weapons float64) float64 { } func (sg ShipGroup) UpgradeShieldsCost(st *ShipType, shields float64) float64 { - return (1 - sg.TechLevel(TechShields).F()/shields) * 10 * st.Shields + return (1 - sg.TechLevel(TechShields).F()/shields) * 10 * st.Shields.F() } func (sg ShipGroup) UpgradeCargoCost(st *ShipType, cargo float64) float64 { - return (1 - sg.TechLevel(TechCargo).F()/cargo) * 10 * st.Cargo + return (1 - sg.TechLevel(TechCargo).F()/cargo) * 10 * st.Cargo.F() } // Мощность бомбардировки func (sg ShipGroup) BombingPower(st *ShipType) float64 { - return (math.Sqrt(st.Weapons*sg.TechLevel(TechWeapons).F())/10. + 1.) * - st.Weapons * + return (math.Sqrt(st.Weapons.F()*sg.TechLevel(TechWeapons).F())/10. + 1.) * + st.Weapons.F() * sg.TechLevel(TechWeapons).F() * float64(st.Armament) * float64(sg.Number) diff --git a/internal/model/game/group_test.go b/internal/model/game/group_test.go index a7e87c0..5f789af 100644 --- a/internal/model/game/group_test.go +++ b/internal/model/game/group_test.go @@ -17,15 +17,15 @@ func TestCargoCapacity(t *testing.T) { Armament: 1, Weapons: 1, Shields: 1, - Cargo: cargoSize, + Cargo: game.F(cargoSize), } sg := game.ShipGroup{ Number: 1, - Tech: map[game.Tech]float64{ - game.TechDrive: 1.5, - game.TechWeapons: 1.1, - game.TechShields: 2.0, - game.TechCargo: 1.0, + Tech: map[game.Tech]game.Float{ + game.TechDrive: game.F(1.5), + game.TechWeapons: game.F(1.1), + game.TechShields: game.F(2.0), + game.TechCargo: game.F(1.0), }, } assert.Equal(t, expectCapacity, sg.CargoCapacity(&ship)) @@ -48,11 +48,11 @@ func TestCarryingAndFullMass(t *testing.T) { } sg := &game.ShipGroup{ Number: 1, - Tech: map[game.Tech]float64{ - game.TechDrive: 1.0, - game.TechWeapons: 1.0, - game.TechShields: 1.0, - game.TechCargo: 1.0, + Tech: map[game.Tech]game.Float{ + game.TechDrive: game.F(1.0), + game.TechWeapons: game.F(1.0), + game.TechShields: game.F(1.0), + game.TechCargo: game.F(1.0), }, Load: 0.0, } @@ -80,11 +80,11 @@ func TestSpeed(t *testing.T) { } sg := &game.ShipGroup{ Number: 1, - Tech: map[game.Tech]float64{ - game.TechDrive: 1.0, - game.TechWeapons: 1.0, - game.TechShields: 1.0, - game.TechCargo: 1.0, + Tech: map[game.Tech]game.Float{ + game.TechDrive: game.F(1.0), + game.TechWeapons: game.F(1.0), + game.TechShields: game.F(1.0), + game.TechCargo: game.F(1.0), }, Load: 0.0, } @@ -109,11 +109,11 @@ func TestBombingPower(t *testing.T) { } sg := game.ShipGroup{ Number: 1, - Tech: map[game.Tech]float64{ - game.TechDrive: 1.0, - game.TechWeapons: 1.0, - game.TechShields: 1.0, - game.TechCargo: 1.0, + Tech: map[game.Tech]game.Float{ + game.TechDrive: game.F(1.0), + game.TechWeapons: game.F(1.0), + game.TechShields: game.F(1.0), + game.TechCargo: game.F(1.0), }, } assert.Equal(t, 139.295, number.Fixed3(sg.BombingPower(&BattleStation))) @@ -123,9 +123,9 @@ func TestBombingPower(t *testing.T) { func TestDriveEffective(t *testing.T) { tc := []struct { - driveShipType float64 - driveTech float64 - expectDriveEffective float64 + driveShipType game.Float + driveTech game.Float + expectDriveEffective game.Float }{ {1, 1, 1}, {1, 2, 2}, @@ -139,20 +139,20 @@ func TestDriveEffective(t *testing.T) { someShip := game.ShipType{ Drive: tc[i].driveShipType, Armament: rand.UintN(30) + 1, - Weapons: rand.Float64()*30 + 1, - Shields: rand.Float64()*100 + 1, - Cargo: rand.Float64()*20 + 1, + Weapons: game.F(rand.Float64()*30 + 1), + Shields: game.F(rand.Float64()*100 + 1), + Cargo: game.F(rand.Float64()*20 + 1), } sg := game.ShipGroup{ Number: rand.UintN(4) + 1, - Tech: map[game.Tech]float64{ + Tech: map[game.Tech]game.Float{ game.TechDrive: tc[i].driveTech, - game.TechWeapons: rand.Float64()*5 + 1, - game.TechShields: rand.Float64()*5 + 1, - game.TechCargo: rand.Float64()*5 + 1, + game.TechWeapons: game.F(rand.Float64()*5 + 1), + game.TechShields: game.F(rand.Float64()*5 + 1), + game.TechCargo: game.F(rand.Float64()*5 + 1), }, } - assert.Equal(t, tc[i].expectDriveEffective, sg.DriveEffective(&someShip)) + assert.Equal(t, tc[i].expectDriveEffective.F(), sg.DriveEffective(&someShip)) } } @@ -170,7 +170,7 @@ func TestShipGroupEqual(t *testing.T) { FleetID: &fleetId, CargoType: &mat, Load: 123.45, - Tech: map[game.Tech]float64{ + Tech: map[game.Tech]game.Float{ game.TechDrive: 1.0, game.TechWeapons: 1.0, game.TechShields: 1.0, diff --git a/internal/model/game/group_upgrade.go b/internal/model/game/group_upgrade.go deleted file mode 100644 index 7b72899..0000000 --- a/internal/model/game/group_upgrade.go +++ /dev/null @@ -1,90 +0,0 @@ -package game - -import ( - "math" - "slices" -) - -type UpgradeCalc struct { - Cost map[Tech]float64 -} - -func (uc UpgradeCalc) UpgradeCost(ships uint) float64 { - var sum float64 - for _, v := range uc.Cost { - sum += v - } - return sum * float64(ships) -} - -func (uc UpgradeCalc) UpgradeMaxShips(resources float64) uint { - return uint(math.Floor(resources / uc.UpgradeCost(1))) -} - -func BlockUpgradeCost(blockMass, currentBlockTech, targetBlockTech float64) float64 { - if blockMass == 0 || targetBlockTech <= currentBlockTech { - return 0 - } - return (1 - currentBlockTech/targetBlockTech) * 10 * blockMass -} - -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) - } - if weapons > 0 { - uc.Cost[TechWeapons] = BlockUpgradeCost(st.WeaponsBlockMass(), sg.TechLevel(TechWeapons).F(), weapons) - } - if shields > 0 { - uc.Cost[TechShields] = BlockUpgradeCost(st.ShieldsBlockMass(), sg.TechLevel(TechShields).F(), shields) - } - if cargo > 0 { - uc.Cost[TechCargo] = BlockUpgradeCost(st.CargoBlockMass(), sg.TechLevel(TechCargo).F(), cargo) - } - return *uc -} - -func CurrentUpgradingLevel(sg *ShipGroup, tech Tech) float64 { - if sg.StateUpgrade == nil { - return 0 - } - ti := slices.IndexFunc(sg.StateUpgrade.UpgradeTech, func(pref UpgradePreference) bool { return pref.Tech == tech }) - if ti >= 0 { - return sg.StateUpgrade.UpgradeTech[ti].Level - } - return 0 -} - -func FutureUpgradeLevel(raceLevel, groupLevel, limit float64) float64 { - target := limit - if target == 0 || target > raceLevel { - target = raceLevel - } - if groupLevel == target { - return 0 - } - return target -} - -func UpgradeGroupPreference(sg ShipGroup, st ShipType, tech Tech, v float64) ShipGroup { - if v <= 0 || st.BlockMass(tech) == 0 || sg.TechLevel(tech).F() >= v { - return sg - } - var su InUpgrade - if sg.StateUpgrade != nil { - su = *sg.StateUpgrade - } else { - su = InUpgrade{UpgradeTech: []UpgradePreference{}} - } - ti := slices.IndexFunc(su.UpgradeTech, func(pref UpgradePreference) bool { return pref.Tech == tech }) - if ti < 0 { - su.UpgradeTech = append(su.UpgradeTech, UpgradePreference{Tech: tech}) - ti = len(su.UpgradeTech) - 1 - } - su.UpgradeTech[ti].Level = v - su.UpgradeTech[ti].Cost = BlockUpgradeCost(st.BlockMass(tech), sg.TechLevel(tech).F(), v) * float64(sg.Number) - - sg.StateUpgrade = &su - return sg -} diff --git a/internal/model/game/group_upgrade_test.go b/internal/model/game/group_upgrade_test.go deleted file mode 100644 index 84a8f99..0000000 --- a/internal/model/game/group_upgrade_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package game_test - -import ( - "testing" - - "github.com/iliadenisov/galaxy/internal/model/game" - "github.com/stretchr/testify/assert" -) - -var ( - Cruiser = game.ShipType{ - Name: "Cruiser", - Drive: 15, - Armament: 1, - Weapons: 15, - Shields: 15, - Cargo: 0, - } -) - -func TestBlockUpgradeCost(t *testing.T) { - assert.Equal(t, 00.0, game.BlockUpgradeCost(1, 1.0, 1.0)) - assert.Equal(t, 25.0, game.BlockUpgradeCost(5, 1.0, 2.0)) - assert.Equal(t, 50.0, game.BlockUpgradeCost(10, 1.0, 2.0)) -} - -func TestGroupUpgradeCost(t *testing.T) { - sg := &game.ShipGroup{ - Tech: map[game.Tech]float64{ - game.TechDrive: 1.0, - game.TechWeapons: 1.0, - game.TechShields: 1.0, - game.TechCargo: 1.0, - }, - Number: 1, - } - assert.Equal(t, 225.0, game.GroupUpgradeCost(sg, Cruiser, 2.0, 2.0, 2.0, 2.0).UpgradeCost(1)) -} - -func TestUpgradeMaxShips(t *testing.T) { - sg := &game.ShipGroup{ - Tech: map[game.Tech]float64{ - game.TechDrive: 1.0, - game.TechWeapons: 1.0, - game.TechShields: 1.0, - game.TechCargo: 1.0, - }, - Number: 10, - } - uc := game.GroupUpgradeCost(sg, Cruiser, 2.0, 2.0, 2.0, 2.0) - assert.Equal(t, uint(4), uc.UpgradeMaxShips(1000)) -} - -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)) - - 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)) - - 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)) -} - -func TestFutureUpgradeLevel(t *testing.T) { - assert.Equal(t, 0.0, game.FutureUpgradeLevel(2.0, 2.0, 2.0)) - assert.Equal(t, 0.0, game.FutureUpgradeLevel(2.0, 2.0, 3.0)) - assert.Equal(t, 1.5, game.FutureUpgradeLevel(1.5, 2.0, 3.0)) - assert.Equal(t, 2.0, game.FutureUpgradeLevel(2.5, 1.0, 2.0)) - assert.Equal(t, 2.5, game.FutureUpgradeLevel(2.5, 1.0, 0.0)) -} - -func TestUpgradeGroupPreference(t *testing.T) { - sg := game.ShipGroup{ - Number: 4, - Tech: game.TechSet{ - game.TechDrive: 1.0, - game.TechWeapons: 1.0, - game.TechShields: 1.0, - game.TechCargo: 1.0, - }, - } - assert.Nil(t, sg.StateUpgrade) - sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechDrive, 0) - assert.Nil(t, sg.StateUpgrade) - - sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechDrive, 2.0) - assert.NotNil(t, sg.StateUpgrade) - assert.Equal(t, 300., sg.StateUpgrade.TechCost(game.TechDrive)) - assert.Equal(t, 300., sg.StateUpgrade.Cost()) - - sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechWeapons, 2.0) - assert.NotNil(t, sg.StateUpgrade) - assert.Equal(t, 300., sg.StateUpgrade.TechCost(game.TechWeapons)) - assert.Equal(t, 600., sg.StateUpgrade.Cost()) - - sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechShields, 2.0) - assert.NotNil(t, sg.StateUpgrade) - assert.Equal(t, 300., sg.StateUpgrade.TechCost(game.TechShields)) - assert.Equal(t, 900., sg.StateUpgrade.Cost()) - - sg = game.UpgradeGroupPreference(sg, Cruiser, game.TechCargo, 2.0) - assert.NotNil(t, sg.StateUpgrade) - assert.Equal(t, 0., sg.StateUpgrade.TechCost(game.TechCargo)) - assert.Equal(t, 900., sg.StateUpgrade.Cost()) -} diff --git a/internal/model/game/production.go b/internal/model/game/production.go index bfe8b77..8f77885 100644 --- a/internal/model/game/production.go +++ b/internal/model/game/production.go @@ -23,7 +23,7 @@ const ( type Production struct { Type ProductionType `json:"type"` SubjectID *uuid.UUID `json:"subjectId"` // TODO: get rid of Nils? - Progress *float64 `json:"progress"` + Progress *Float `json:"progress"` } func (p ProductionType) AsType(subject uuid.UUID) Production { diff --git a/internal/model/game/race.go b/internal/model/game/race.go index 95a1233..231433a 100644 --- a/internal/model/game/race.go +++ b/internal/model/game/race.go @@ -3,19 +3,15 @@ package game import "github.com/google/uuid" type Race struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Extinct bool `json:"extinct"` - - Votes float64 `json:"votes"` + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Extinct bool `json:"extinct"` + Votes Float `json:"votes"` VoteFor uuid.UUID `json:"voteFor"` Relations []RaceRelation `json:"relations"` - - Tech TechSet `json:"tech"` - - Sciences []Science `json:"science,omitempty"` - - ShipTypes []ShipType `json:"shipType,omitempty"` + Tech TechSet `json:"tech"` + Sciences []Science `json:"science,omitempty"` + ShipTypes []ShipType `json:"shipType,omitempty"` } type Relation string diff --git a/internal/model/game/science.go b/internal/model/game/science.go index 857a6f5..a4e98e5 100644 --- a/internal/model/game/science.go +++ b/internal/model/game/science.go @@ -7,8 +7,8 @@ import ( type Science struct { 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"` + Drive Float `json:"drive"` + Weapons Float `json:"weapons"` + Shields Float `json:"shields"` + Cargo Float `json:"cargo"` } diff --git a/internal/model/game/settings.go b/internal/model/game/settings.go deleted file mode 100644 index 2688c56..0000000 --- a/internal/model/game/settings.go +++ /dev/null @@ -1,7 +0,0 @@ -package game - -type GameParameter struct { - Series string - Players uint - Public bool -} diff --git a/internal/model/game/ship.go b/internal/model/game/ship.go index 0132ddb..da8390c 100644 --- a/internal/model/game/ship.go +++ b/internal/model/game/ship.go @@ -7,11 +7,11 @@ import ( type ShipType struct { ID uuid.UUID `json:"id"` Name string `json:"name"` - Drive float64 `json:"drive"` + Drive Float `json:"drive"` Armament uint `json:"armament"` - Weapons float64 `json:"weapons"` - Shields float64 `json:"shields"` - Cargo float64 `json:"cargo"` + Weapons Float `json:"weapons"` + Shields Float `json:"shields"` + Cargo Float `json:"cargo"` } func (st ShipType) Equal(o ShipType) bool { @@ -38,22 +38,22 @@ func (st ShipType) BlockMass(t Tech) float64 { } func (st ShipType) DriveBlockMass() float64 { - return st.Drive + return st.Drive.F() } func (st ShipType) WeaponsBlockMass() float64 { if st.Armament == 0 || st.Weapons == 0 { return 0 } - return float64(st.Armament+1) * (st.Weapons / 2) + return float64(st.Armament+1) * (st.Weapons.F() / 2) } func (st ShipType) ShieldsBlockMass() float64 { - return st.Shields + return st.Shields.F() } func (st ShipType) CargoBlockMass() float64 { - return st.Cargo + return st.Cargo.F() } func (st ShipType) EmptyMass() float64 {