refactor: floats, tests

This commit is contained in:
Ilia Denisov
2026-02-04 18:33:38 +02:00
parent 9d46abe805
commit 6a603ea9ad
37 changed files with 381 additions and 722 deletions
+2 -2
View File
@@ -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)),
)
+8 -8
View File
@@ -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)
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+6 -6
View File
@@ -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))
+1 -1
View File
@@ -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,
+9 -9
View File
@@ -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
}
+11 -7
View File
@@ -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)
+20 -20
View File
@@ -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())
+4 -4
View File
@@ -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)
+4 -4
View File
@@ -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
}
+8 -8
View File
@@ -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) {
+4 -4
View File
@@ -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()
+9 -9
View File
@@ -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
}
+4 -4
View File
@@ -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)
+1 -1
View File
@@ -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)
+94 -12
View File
@@ -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
}
+116 -1
View File
@@ -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))
}
+4 -3
View File
@@ -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 {