feat: store battles and bombings

This commit is contained in:
Ilia Denisov
2026-01-30 18:57:43 +03:00
parent 824f6609ab
commit 4c14234afb
16 changed files with 274 additions and 103 deletions
+11 -2
View File
@@ -14,6 +14,7 @@ type Battle struct {
ID uuid.UUID
Planet uint
observerGroups map[int]bool // True = In_Battle, False = Out_Battle
initialNumbers map[int]uint // Initial number of ships in the group
Protocol []BattleAction
shipAmmo map[int]uint
@@ -22,7 +23,7 @@ type Battle struct {
type BattleAction struct {
Attacker int
Defenter int
Defender int
Destroyed bool
}
@@ -94,6 +95,11 @@ func ProduceBattles(c *Cache) []*Battle {
result := make([]*Battle, 0)
// TODO: check this behavior:
// Multiple battles on single planet shoul be produced as single battle too:
// A <--> B
// C <--> D
// where: A in peace with [C, D], B in peace with [C, D], and so on.
for pl, observerGroups := range planetGroups {
battleGroups := FilterBattleGroups(c, observerGroups)
b := &Battle{
@@ -102,6 +108,9 @@ func ProduceBattles(c *Cache) []*Battle {
attacker: make(map[int]map[int]float64),
shipAmmo: make(map[int]uint),
}
for sgi := range observerGroups {
b.initialNumbers[sgi] = c.ShipGroup(sgi).Number
}
for i := range battleGroups {
attIdx := battleGroups[i]
@@ -159,7 +168,7 @@ func SingleBattle(c *Cache, b *Battle) {
b.Protocol = append(b.Protocol, BattleAction{
Attacker: attIdx,
Defenter: defIdx,
Defender: defIdx,
Destroyed: destroyed,
})
+57 -23
View File
@@ -1,46 +1,80 @@
package controller
import "github.com/iliadenisov/galaxy/internal/model/game"
import (
"github.com/google/uuid"
"github.com/iliadenisov/galaxy/internal/model/game"
"github.com/iliadenisov/galaxy/internal/model/report"
)
func TransformBattle(c *Cache, b *Battle) *game.BattleReport {
r := &game.BattleReport{
func TransformBattle(c *Cache, b *Battle) *report.BattleReport {
r := &report.BattleReport{
ID: b.ID,
Planet: b.Planet,
PlanetName: c.MustPlanet(b.Planet).Name,
Races: make(map[int]string),
Ships: make(map[int]string),
Protocol: make([]game.BattleActionReport, len(b.Protocol)),
Races: make(map[int]uuid.UUID),
Ships: make(map[int]report.BattleReportGroup),
Protocol: make([]report.BattleActionReport, len(b.Protocol)),
}
cacheShipClass := make(map[string]int)
cacheRaceName := make(map[string]int)
cacheShipClass := make(map[uuid.UUID]int)
cacheRaceName := make(map[uuid.UUID]int)
cacher := func(shipClass string, cache map[string]int) int {
if v, ok := cache[shipClass]; ok {
addShipGroup := func(groupId int, inBattle bool) int {
shipClass := c.ShipGroupShipClass(groupId)
sg := c.ShipGroup(groupId)
itemNumber := len(r.Ships)
r.Ships[itemNumber] = report.BattleReportGroup{
OwnerID: sg.OwnerID,
InBattle: inBattle,
Number: b.initialNumbers[groupId],
NumberLeft: sg.Number,
ClassName: shipClass.Name,
LoadType: sg.CargoString(),
LoadQuantity: sg.Load,
Drive: sg.TechLevel(game.TechDrive),
Weapons: sg.TechLevel(game.TechWeapons),
Shields: sg.TechLevel(game.TechShields),
Cargo: sg.TechLevel(game.TechCargo),
}
cacheShipClass[shipClass.ID] = itemNumber
return itemNumber
}
ship := func(groupId int) int {
shipClass := c.ShipGroupShipClass(groupId)
if v, ok := cacheShipClass[shipClass.ID]; ok {
return v
} else {
itemNumber := len(r.Ships)
r.Ships[itemNumber] = shipClass
cache[shipClass] = itemNumber
return addShipGroup(groupId, true)
}
}
race := func(groupId int) int {
race := c.ShipGroupOwnerRace(groupId)
if v, ok := cacheRaceName[race.ID]; ok {
return v
} else {
itemNumber := len(r.Races)
r.Races[itemNumber] = race.ID
cacheRaceName[race.ID] = itemNumber
return itemNumber
}
}
for i := range b.Protocol {
r.Protocol[i] = game.BattleActionReport{
Attacker: cacher(c.ShipGroupOwnerRace(b.Protocol[i].Attacker).Name, cacheRaceName),
AttackerShipClass: cacher(c.ShipGroupShipClass(b.Protocol[i].Attacker).Name, cacheShipClass),
Defender: cacher(c.ShipGroupOwnerRace(b.Protocol[i].Defenter).Name, cacheRaceName),
DefenderShipClass: cacher(c.ShipGroupShipClass(b.Protocol[i].Defenter).Name, cacheShipClass),
r.Protocol[i] = report.BattleActionReport{
Attacker: race(b.Protocol[i].Attacker),
AttackerShipClass: ship(b.Protocol[i].Attacker),
Defender: race(b.Protocol[i].Defender),
DefenderShipClass: ship(b.Protocol[i].Defender),
Destroyed: b.Protocol[i].Destroyed,
}
}
for name, index := range cacheRaceName {
r.Races[index] = name
}
for name, index := range cacheShipClass {
r.Ships[index] = name
for sgi, inBattle := range b.observerGroups {
if !inBattle {
addShipGroup(sgi, false)
}
}
return r
+5 -24
View File
@@ -3,36 +3,17 @@ package controller
import (
"github.com/google/uuid"
"github.com/iliadenisov/galaxy/internal/model/game"
"github.com/iliadenisov/galaxy/internal/model/report"
)
type BombingReport struct {
Planets []BombingPlanetReport `json:"planets"`
}
type BombingPlanetReport struct {
ID uuid.UUID `json:"id"`
Planet string `json:"name"`
Number uint `json:"number"`
Owner string `json:"owner"`
Attacker string `json:"attacker"`
Production string `json:"production"`
Industry float64 `json:"industry"` // I - Промышленность
Population float64 `json:"population"` // P - Население
Colonists float64 `json:"colonists"` // COL C - Количество колонистов
Capital float64 `json:"capital"` // CAP $ - Запасы промышленности
Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырья
AttackPower float64 `json:"attack"`
Wiped bool `json:"wiped"`
}
func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) BombingPlanetReport {
func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) report.BombingPlanetReport {
attackPower := 0.
for _, i := range groups {
sg := c.ShipGroup(i)
st := c.ShipGroupShipClass(i)
attackPower += sg.BombingPower(st)
}
r := &BombingPlanetReport{
r := &report.BombingPlanetReport{
ID: uuid.New(),
Planet: p.Name,
Number: p.Number,
@@ -51,8 +32,8 @@ func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) BombingPlane
return *r
}
func (c *Cache) ProduceBombings() []BombingPlanetReport {
report := make([]BombingPlanetReport, 0)
func (c *Cache) ProduceBombings() []report.BombingPlanetReport {
report := make([]report.BombingPlanetReport, 0)
for pn, enemies := range c.collectBombingGroups() {
p := c.MustPlanet(pn)
for ri, groups := range enemies {
+6 -2
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/iliadenisov/galaxy/internal/model/game"
"github.com/iliadenisov/galaxy/internal/model/report"
"github.com/iliadenisov/galaxy/internal/repo"
)
@@ -26,8 +27,11 @@ type Repo interface {
// LoadStateSafe retrieves game current state without preliminary locking
LoadStateSafe() (*game.Game, error)
// SaveBattle stores
SaveBattle(t uint, b *game.BattleReport) error
// SaveBattle stores a new battle protocol and battle meta data for turn t
SaveBattle(t uint, b *report.BattleReport, m *game.BattleMeta) error
// SaveBombing stores all prodused bombings for turn t
SaveBombings(t uint, b []report.BombingPlanetReport) error
}
type Controller struct {
+1 -1
View File
@@ -41,7 +41,7 @@ func buildGameOnMap(races []string, m generator.Map) (*game.Game, error) {
}
g := &game.Game{
ID: gameID,
Age: 0,
Turn: 0,
Race: make([]game.Race, len(races)),
}
gameMap := &game.Map{
+1 -1
View File
@@ -35,7 +35,7 @@ func TestNewGame(t *testing.T) {
g, err := r.LoadState()
assert.NoError(t, err)
assert.Equal(t, gameID, g.ID)
assert.Equal(t, uint(0), g.Age)
assert.Equal(t, uint(0), g.Turn)
assert.Equal(t, players, len(g.Race))
for r := range g.Race {
+32 -6
View File
@@ -2,12 +2,16 @@ package controller
import (
// "github.com/iliadenisov/galaxy/internal/game/battle"
"maps"
"slices"
"github.com/google/uuid"
"github.com/iliadenisov/galaxy/internal/model/game"
)
func MakeTurn(c *Controller, r Repo, g *game.Game) error {
func MakeTurn(c *Controller, r Repo) error {
// Next turn
g.Age += 1
c.Cache.g.Turn += 1
// 01. Корабли, где это возможно, объединяются в группы.
c.Cache.TurnMergeEqualShipGroups()
@@ -28,7 +32,7 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error {
battles = append(battles, ProduceBattles(c.Cache)...)
// 07. Корабли бомбят вражеские планеты.
_ = c.Cache.ProduceBombings()
bombings := c.Cache.ProduceBombings()
// 08. На планетах строятся корабли.
// 09. Корабли, где это возможно, объединяются в группы.
@@ -49,12 +53,33 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error {
/*** Last steps ***/
// Store bombings
if len(bombings) > 0 {
if err := r.SaveBombings(c.Cache.g.Turn, bombings); err != nil {
return err
}
}
// Store battles
if len(battles) > 0 {
battleMeta := make([]game.BattleMeta, len(battles))
for i := range battles {
// TODO: add In_Battle / Out_Battle participants?
br := TransformBattle(c.Cache, battles[i])
if err := r.SaveBattle(g.Age, br); err != nil {
b := battles[i]
observers := make(map[uuid.UUID]bool)
for sgi := range b.observerGroups {
observers[c.Cache.ShipGroup(sgi).OwnerID] = true
}
battleMeta[i] = game.BattleMeta{
Turn: c.Cache.g.Turn,
Planet: b.Planet,
BattleID: b.ID,
ObserverIDs: slices.Collect(maps.Keys(observers)),
}
report := TransformBattle(c.Cache, b)
if err := r.SaveBattle(c.Cache.g.Turn, report, &battleMeta[i]); err != nil {
return err
}
}
@@ -68,5 +93,6 @@ func MakeTurn(c *Controller, r Repo, g *game.Game) error {
// TODO: Store individual reports
_ = winners
// [ ] monitor memory consumption at this point?
return nil
}
+1
View File
@@ -105,6 +105,7 @@ func (c *Cache) ShipGroupOwnerRace(groupIndex int) *game.Race {
func (c *Cache) ShipGroupNumber(i int, n uint) {
c.validateShipGroupIndex(i)
c.g.ShipGroups[i].Number = n
// FIXME: cargo load must be decreased proportionally
}
func (c *Cache) DeleteShipGroup(i int) {