Files
galaxy-game/game/internal/controller/bombing.go
T
Ilia Denisov a01f39e4a7
Tests · Go / test (push) Successful in 1m57s
Tests · Integration / integration (pull_request) Successful in 1m46s
Tests · Go / test (pull_request) Successful in 2m2s
fix(game): bomb in descending power order, collapse industry on wipe
Per the rules ("Бомбардировка планет"), a planet is bombed from the
strongest attacking power downwards, and a planet bombed to extinction
keeps its material and capital stockpiles but loses its working industry.

ProduceBombings now sorts attacking races by total bombing power
(descending) instead of iterating the attacker map in random order, and
on a wipe zeroes the planet's industry (Free already keeps capital and
material). bombingPower is extracted as a shared helper.

The rules already describe both, so no documentation change. Tests:
bombing order by power, and industry collapse with capital/material kept
on a wipe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 08:47:45 +02:00

131 lines
3.6 KiB
Go

package controller
import (
"cmp"
"maps"
"slices"
"galaxy/game/internal/model/game"
"github.com/google/uuid"
)
func (c *Cache) ProduceBombings() []*game.Bombing {
report := make([]*game.Bombing, 0)
for pn, enemies := range c.collectBombingGroups() {
p := c.MustPlanet(pn)
if !p.Owned() {
continue
}
// The planet is hit by all attacking races at once, accounted from the
// strongest bombing power downwards, until no population remains.
attackers := slices.Collect(maps.Keys(enemies))
slices.SortFunc(attackers, func(a, b int) int {
return cmp.Compare(c.bombingPower(enemies[b]), c.bombingPower(enemies[a]))
})
for _, ri := range attackers {
br := c.bombingReport(p, ri, enemies[ri])
report = append(report, br)
if br.Wiped {
break
}
}
if p.Population == 0 {
// Wiped out: the planet turns uninhabited and its industry
// collapses, but the material and capital stockpiles survive for
// whoever colonises it next (rules "Бомбардировка планет").
p.Free()
p.Ind(0)
} else {
// Если на планете остались также и колонисты, то они превращаются в население,
// а накопленная промышленность возмещает потери производства.
p.UnpackColonists()
p.UnpackCapital()
}
}
return report
}
func (c *Cache) bombingPower(groups []int) float64 {
var power float64
for _, i := range groups {
power += c.ShipGroup(i).BombingPower(c.ShipGroupShipClass(i))
}
return power
}
func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) *game.Bombing {
attackPower := c.bombingPower(groups)
r := &game.Bombing{
ID: uuid.New(),
PlanetOwnedID: *p.Owner,
Planet: p.Name,
Number: p.Number,
Owner: c.g.Race[c.RaceIndex(*p.Owner)].Name,
Attacker: c.g.Race[ri].Name,
Production: c.PlanetProductionDisplayName(p.Number),
Industry: p.Industry,
Population: p.Population,
Colonists: p.Colonists,
Capital: p.Capital,
Material: p.Material,
AttackPower: game.F(attackPower),
}
bombPlanet(p, attackPower)
r.Wiped = p.Population == 0
return r
}
func bombPlanet(p *game.Planet, power float64) {
// Уничтожается население и колонисты в количестве равном [суммарной] мощности бомбардировки
if power > p.Population.F() {
p.Pop(0)
} else {
p.Pop(p.Population.F() - power)
}
if power > p.Colonists.F() {
p.Col(0)
} else {
p.Col(p.Colonists.F() - power)
}
// Такое же количество промышленности превращается в сырье
if power > p.Industry.F() {
p.Mat(p.Material.F() + p.Industry.F())
p.Ind(0)
} else {
p.Mat(p.Material.F() + power)
p.Ind(p.Industry.F() - power)
}
}
// [planet_num] -> [enemy_race_id] -> []group_id
func (c *Cache) collectBombingGroups() map[uint]map[int][]int {
result := make(map[uint]map[int][]int)
for i := range c.ShipGroupsIndex() {
sg := c.ShipGroup(i)
if sg.State() != game.StateInOrbit {
continue
}
st := c.ShipGroupShipClass(i)
if st.WeaponsBlockMass() == 0 {
continue
}
p := c.MustPlanet(sg.Destination)
if p.OwnedBy(sg.OwnerID) || !p.Owned() {
continue
}
r1 := c.RaceIndex(sg.OwnerID)
r2 := c.RaceIndex(*p.Owner)
if c.Relation(r1, r2) == game.RelationPeace {
continue
}
// add result
if _, ok := result[p.Number]; !ok {
result[p.Number] = make(map[int][]int)
}
result[p.Number][r1] = append(result[p.Number][r1], i)
}
return result
}