From 6157c07a35453f38fe6cc73b70024ffabab9233d Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Thu, 25 Dec 2025 21:32:19 +0300 Subject: [PATCH] cmd: disassebmle group --- internal/model/game/game_test.go | 24 +++++---- internal/model/game/group.go | 85 ++++++++++++++++++++++++++++-- internal/model/game/group_test.go | 86 ++++++++++++++++++++++++++++++- internal/model/game/planet.go | 18 +++++-- 4 files changed, 194 insertions(+), 19 deletions(-) diff --git a/internal/model/game/game_test.go b/internal/model/game/game_test.go index 1578a51..0a1be9f 100644 --- a/internal/model/game/game_test.go +++ b/internal/model/game/game_test.go @@ -41,16 +41,22 @@ var ( }, Map: Map, } - Race_0_idx = 0 - Race_0_Gunship = "R0_Gunship" - Race_0_Freighter = "R0_Freighter" - R0_Planet_0_num uint = 0 - R0_Planet_2_num uint = 2 + Race_0_idx = 0 + Race_0_Gunship = "R0_Gunship" + Race_0_Freighter = "R0_Freighter" + R0_Planet_0_num uint = 0 + R0_Planet_2_num uint = 2 + Race_0_Gunship_idx = 0 + Race_0_Freighter_idx = 1 + Race_0_Cruiser_idx = 2 - Race_1_idx = 1 - Race_1_Gunship = "R1_Gunship" - Race_1_Freighter = "R1_Freighter" - R1_Planet_1_num uint = 1 + Race_1_idx = 1 + Race_1_Gunship = "R1_Gunship" + Race_1_Freighter = "R1_Freighter" + R1_Planet_1_num uint = 1 + Race_1_Gunship_idx = 0 + Race_1_Freighter_idx = 1 + Race_1_Cruiser_idx = 2 ShipType_Cruiser = "Cruiser" ) diff --git a/internal/model/game/group.go b/internal/model/game/group.go index badff67..7a131ae 100644 --- a/internal/model/game/group.go +++ b/internal/model/game/group.go @@ -81,10 +81,15 @@ func (sg ShipGroup) CarryingMass() float64 { return sg.Load / sg.Cargo } -// Полная масса - -// массу корабля самого по себе плюс масса перевозимого груза. +// Масса группы без учёта груза +func (sg ShipGroup) EmptyMass(st *ShipType) float64 { + return st.EmptyMass() * float64(sg.Number) +} + +// Полная масса - +// массу корабля самого по себе плюс масса перевозимого груза func (sg ShipGroup) FullMass(st *ShipType) float64 { - return st.EmptyMass() + sg.CarryingMass() + return sg.EmptyMass(st) + sg.CarryingMass() } // Эффективность двигателя - @@ -94,7 +99,7 @@ func (sg ShipGroup) DriveEffective(st *ShipType) float64 { } // Корабли перемещаются за один ход на количество световых лет, равное -// эффективности двигателя, умноженной на 20 и деленной на "Полную массу" корабля. +// эффективности двигателя, умноженной на 20 и деленной на "Полную массу" корабля func (sg ShipGroup) Speed(st *ShipType) float64 { return sg.DriveEffective(st) * 20 / sg.FullMass(st) } @@ -145,6 +150,78 @@ func (g *Game) BreakGroup(raceName string, groupIndex, quantity uint) error { return g.breakGroupInternal(ri, groupIndex, quantity) } +func (g *Game) DisassembleGroup(raceName string, groupIndex, quantity uint) error { + ri, err := g.raceIndex(raceName) + if err != nil { + return err + } + return g.disassembleGroupInternal(ri, groupIndex, quantity) +} + +func (g *Game) disassembleGroupInternal(ri int, groupIndex, quantity uint) error { + sgi := -1 + var maxIndex uint + for i, sg := range g.listIndexShipGroups(ri) { + if sgi < 0 && sg.Index == groupIndex { + sgi = i + } + if sg.Index > maxIndex { + maxIndex = sg.Index + } + } + if sgi < 0 { + return e.NewEntityNotExistsError("group #%d", groupIndex) + } + + if g.ShipGroups[sgi].State != "In_Orbit" || g.ShipGroups[sgi].Origin != nil || g.ShipGroups[sgi].Range != nil { + return e.NewShipsBusyError() + } + + if g.ShipGroups[sgi].Number < quantity { + return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity) + } + + pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination }) + if pl < 0 { + return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination) + } + var sti int + if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 { + // hard to test, need manual game data invalidation + return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID) + } + + if quantity > 0 && quantity < g.ShipGroups[sgi].Number { + // make new group for disassembly + nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity) + if err != nil { + return err + } + sgi = nsgi + } + + if g.ShipGroups[sgi].CargoType != nil { + ct := *g.ShipGroups[sgi].CargoType + load := g.ShipGroups[sgi].Load + switch ct { + case CargoColonist: + if g.Map.Planet[pl].Owner == g.Race[ri].ID { + g.Map.Planet[pl] = UnloadColonists(g.Map.Planet[pl], load) + } + case CargoMaterial: + g.Map.Planet[pl].Material += load + case CargoCapital: + g.Map.Planet[pl].Capital += load + } + } + + g.Map.Planet[pl].Material += g.ShipGroups[sgi].EmptyMass(&g.Race[ri].ShipTypes[sti]) + + g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...) + + return nil +} + func (g *Game) LoadCargo(raceName string, groupIndex uint, cargoType string, ships uint, quantity float64) error { ri, err := g.raceIndex(raceName) if err != nil { diff --git a/internal/model/game/group_test.go b/internal/model/game/group_test.go index 6cb6d6e..f81d6a2 100644 --- a/internal/model/game/group_test.go +++ b/internal/model/game/group_test.go @@ -605,7 +605,7 @@ func TestLoadCargo(t *testing.T) { func TestUnloadCargo(t *testing.T) { g := copyGame() - // 1: idx = 0 / Ready to unload COL + // 1: idx = 0 / empty assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) // 2: idx = 1 / Has no cargo bay @@ -697,3 +697,87 @@ func TestUnloadCargo(t *testing.T) { assert.Nil(t, g.ShipGroups[0].CargoType) assert.Equal(t, 0.0, number.Fixed3(g.ShipGroups[0].Load)) } + +func TestDisassembleGroup(t *testing.T) { + g := copyGame() + + // 1: idx = 0 / empty + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) + + // 2: idx = 1 / In_Space + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) + g.ShipGroups[1].Origin = &R0_Planet_2_num + rng := 31.337 + g.ShipGroups[1].Range = &rng + g.ShipGroups[1].State = "In_Space" + + // 3: idx = 2 / loaded with COL + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) + g.ShipGroups[2].CargoType = game.CargoColonist.Ref() + g.ShipGroups[2].Load = 80.0 + + // 4: idx = 3 / on foreign planet / loaded with MAT + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) + g.ShipGroups[3].Destination = R1_Planet_1_num + g.ShipGroups[3].CargoType = game.CargoMaterial.Ref() + g.ShipGroups[3].Load = 100.0 + + // 5: idx = 4 / on foreign planet / loaded with COL + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) + g.ShipGroups[4].Destination = R1_Planet_1_num + g.ShipGroups[4].CargoType = game.CargoColonist.Ref() + g.ShipGroups[4].Load = 2.345 + + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 5) + + // tests + assert.ErrorContains(t, + g.DisassembleGroup("UnknownRace", 1, 0), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.DisassembleGroup(Race_0.Name, 555, 0), + e.GenericErrorText(e.ErrInputEntityNotExists)) + assert.ErrorContains(t, + g.DisassembleGroup(Race_0.Name, 2, 0), + e.GenericErrorText(e.ErrShipsBusy)) + assert.ErrorContains(t, + g.DisassembleGroup(Race_0.Name, 3, 12), + e.GenericErrorText(e.ErrBeakGroupNumberNotEnough)) + + groupEmptyMass := g.ShipGroups[4].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx]) + // groupLoadCOL := g.ShipGroups[4].Load + planetMAT := g.Map.Planet[1].Material + planetCOL := g.Map.Planet[1].Colonists + + assert.NoError(t, g.DisassembleGroup(Race_0.Name, 5, 0)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4) + assert.Equal(t, planetMAT+groupEmptyMass, g.Map.Planet[1].Material) + assert.Equal(t, planetCOL, g.Map.Planet[1].Colonists) + + groupEmptyMass = g.ShipGroups[3].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx]) + groupLoadMAT := g.ShipGroups[3].Load + planetMAT = g.Map.Planet[1].Material + assert.NoError(t, g.DisassembleGroup(Race_0.Name, 4, 0)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3) + assert.Equal(t, planetMAT+groupEmptyMass+groupLoadMAT, g.Map.Planet[1].Material) + + groupEmptyMass = g.ShipGroups[2].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx]) + planetMAT = g.Map.Planet[0].Material + planetCOL = g.Map.Planet[0].Colonists + planetPOP := 90.0 + g.Map.Planet[0].Population = planetPOP + var shipsDisassembling uint = 3 + groupEmptyMass = groupEmptyMass / float64(g.ShipGroups[2].Number) * float64(shipsDisassembling) + newGroupUnloadedCOL := g.ShipGroups[2].Load / float64(g.ShipGroups[2].Number) * float64(shipsDisassembling) + expectPOPIncrease := newGroupUnloadedCOL * 8 + freePOPLeft := g.Map.Planet[0].Size - g.Map.Planet[0].Population + expectAddedCOL := (expectPOPIncrease - freePOPLeft) / 8 + expectAddedPOP := freePOPLeft + assert.NoError(t, g.DisassembleGroup(Race_0.Name, 3, shipsDisassembling)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3) + assert.Equal(t, planetCOL+expectAddedCOL, g.Map.Planet[0].Colonists) + assert.Equal(t, planetPOP+expectAddedPOP, g.Map.Planet[0].Population) + assert.Equal(t, planetMAT+groupEmptyMass, g.Map.Planet[0].Material) + assert.Equal(t, uint(7), g.ShipGroups[2].Number) + assert.Equal(t, 56.0, g.ShipGroups[2].Load) +} diff --git a/internal/model/game/planet.go b/internal/model/game/planet.go index e9cd69e..56c1a62 100644 --- a/internal/model/game/planet.go +++ b/internal/model/game/planet.go @@ -18,7 +18,7 @@ type UninhabitedPlanet struct { UnidentifiedPlanet Size float64 `json:"size"` Name string `json:"name"` - Resources float64 `json:"resources"` // R - Ресурсы / сырьё + Resources float64 `json:"resources"` // R - Ресурсы Capital float64 `json:"capital"` // CAP $ - Запасы промышленности Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырья } @@ -70,13 +70,21 @@ func (p *Planet) IncreaseMaterial() { // Автоматическое увеличение населения на каждом ходу func (p *Planet) IncreasePopulation() { p.Population *= 1.08 - var extraPopulation = p.Size - p.Population - if extraPopulation > 0 { - p.Colonists += extraPopulation / 8 - p.Population -= extraPopulation + if p.Population > p.Size { + p.Colonists += (p.Population - p.Size) / 8 + p.Population = p.Size } } +func UnloadColonists(p Planet, v float64) Planet { + p.Population += v * 8 + if p.Population > p.Size { + p.Colonists += (p.Population - p.Size) / 8 + p.Population = p.Size + } + return p +} + func (g Game) RenamePlanet(raceName string, planetNumber int, typeName string) error { ri, err := g.raceIndex(raceName) if err != nil {