wip: generate report
This commit is contained in:
@@ -10,34 +10,28 @@ import (
|
||||
|
||||
var (
|
||||
attacker = game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Attacker",
|
||||
Drive: 8,
|
||||
Armament: 1,
|
||||
Weapons: 8,
|
||||
Shields: 8,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "Attacker",
|
||||
Drive: 8,
|
||||
Armament: 1,
|
||||
Weapons: 8,
|
||||
Shields: 8,
|
||||
Cargo: 0,
|
||||
}
|
||||
defender = game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Defender",
|
||||
Drive: 1,
|
||||
Armament: 1,
|
||||
Weapons: 1,
|
||||
Shields: 1,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "Defender",
|
||||
Drive: 1,
|
||||
Armament: 1,
|
||||
Weapons: 1,
|
||||
Shields: 1,
|
||||
Cargo: 0,
|
||||
}
|
||||
ship = game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Ship",
|
||||
Drive: 10,
|
||||
Armament: 1,
|
||||
Weapons: 10,
|
||||
Shields: 10,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "Ship",
|
||||
Drive: 10,
|
||||
Armament: 1,
|
||||
Weapons: 10,
|
||||
Shields: 10,
|
||||
Cargo: 0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -24,17 +24,20 @@ func TransformBattle(c *Cache, b *Battle) *report.BattleReport {
|
||||
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.RF(),
|
||||
Drive: sg.TechLevel(game.TechDrive).RF(),
|
||||
Weapons: sg.TechLevel(game.TechWeapons).RF(),
|
||||
Shields: sg.TechLevel(game.TechShields).RF(),
|
||||
Cargo: sg.TechLevel(game.TechCargo).RF(),
|
||||
OwnerID: sg.OwnerID,
|
||||
Race: c.g.Race[c.RaceIndex(sg.OwnerID)].Name,
|
||||
InBattle: inBattle,
|
||||
Number: b.initialNumbers[groupId],
|
||||
NumberLeft: sg.Number,
|
||||
ClassName: shipClass.Name,
|
||||
LoadType: sg.CargoString(),
|
||||
LoadQuantity: report.F(sg.Load.F()),
|
||||
DriveTech: report.F(sg.TechLevel(game.TechDrive).F()),
|
||||
ClassArmament: shipClass.Armament,
|
||||
WeaponsTech: report.F(sg.TechLevel(game.TechWeapons).F()),
|
||||
ShieldsTech: report.F(sg.TechLevel(game.TechShields).F()),
|
||||
CargoTech: report.F(sg.TechLevel(game.TechCargo).F()),
|
||||
ClassMass: report.F(shipClass.EmptyMass()),
|
||||
}
|
||||
cacheShipClass[shipClass.ID] = itemNumber
|
||||
return itemNumber
|
||||
|
||||
@@ -3,37 +3,37 @@ package controller
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/iliadenisov/galaxy/internal/model/report"
|
||||
)
|
||||
|
||||
func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) report.BombingPlanetReport {
|
||||
func (c *Cache) bombingReport(p *game.Planet, ri int, groups []int) *game.Bombing {
|
||||
attackPower := 0.
|
||||
for _, i := range groups {
|
||||
sg := c.ShipGroup(i)
|
||||
st := c.ShipGroupShipClass(i)
|
||||
attackPower += sg.BombingPower(st)
|
||||
}
|
||||
r := &report.BombingPlanetReport{
|
||||
ID: uuid.New(),
|
||||
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.RF(),
|
||||
Population: p.Population.RF(),
|
||||
Colonists: p.Colonists.RF(),
|
||||
Capital: p.Capital.RF(),
|
||||
Material: p.Material.RF(),
|
||||
AttackPower: game.RF(attackPower),
|
||||
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
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *Cache) ProduceBombings() []report.BombingPlanetReport {
|
||||
report := make([]report.BombingPlanetReport, 0)
|
||||
func (c *Cache) ProduceBombings() []*game.Bombing {
|
||||
report := make([]*game.Bombing, 0)
|
||||
for pn, enemies := range c.collectBombingGroups() {
|
||||
p := c.MustPlanet(pn)
|
||||
for ri, groups := range enemies {
|
||||
|
||||
@@ -120,22 +120,22 @@ func TestProduceBombings(t *testing.T) {
|
||||
|
||||
reports := c.ProduceBombings()
|
||||
assert.Len(t, reports, 2)
|
||||
for _, r := range reports {
|
||||
assert.NotEqual(t, uuid.Nil, r.ID)
|
||||
switch pn := r.Number; pn {
|
||||
for _, b := range reports {
|
||||
assert.NotEqual(t, uuid.Nil, b.ID)
|
||||
switch pn := b.Number; pn {
|
||||
case R1_Planet_1_num:
|
||||
assert.Equal(t, Race_1.Name, r.Owner)
|
||||
assert.Equal(t, Race_0.Name, r.Attacker)
|
||||
assert.InDelta(t, 697.857, r.AttackPower.F(), 0.0003)
|
||||
assert.True(t, r.Wiped)
|
||||
assert.Equal(t, Race_1.Name, b.Owner)
|
||||
assert.Equal(t, Race_0.Name, b.Attacker)
|
||||
assert.InDelta(t, 697.857, b.AttackPower.F(), 0.0003)
|
||||
assert.True(t, b.Wiped)
|
||||
assert.Equal(t, uuid.Nil, c.MustPlanet(pn).Owner)
|
||||
assert.Empty(t, c.MustPlanet(pn).Route)
|
||||
assert.Equal(t, 0., c.MustPlanet(pn).Population.F())
|
||||
case R0_Planet_2_num:
|
||||
assert.Equal(t, Race_0.Name, r.Owner)
|
||||
assert.Equal(t, Race_1.Name, r.Attacker)
|
||||
assert.Equal(t, 358.856, r.AttackPower.F())
|
||||
assert.False(t, r.Wiped)
|
||||
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.False(t, b.Wiped)
|
||||
assert.Equal(t, Race_0_ID, c.MustPlanet(pn).Owner)
|
||||
assert.NotEmpty(t, c.MustPlanet(pn).Route)
|
||||
assert.InDelta(t, 500.-358.85596, c.MustPlanet(pn).Population.F(), 0.000001)
|
||||
|
||||
@@ -31,7 +31,7 @@ type Repo interface {
|
||||
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
|
||||
SaveBombings(t uint, b []*game.Bombing) error
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
|
||||
@@ -62,14 +62,12 @@ var (
|
||||
ShipType_Cruiser = "Cruiser"
|
||||
|
||||
Cruiser = game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Cruiser",
|
||||
Drive: 15,
|
||||
Armament: 1,
|
||||
Weapons: 15,
|
||||
Shields: 15,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "Cruiser",
|
||||
Drive: 15,
|
||||
Armament: 1,
|
||||
Weapons: 15,
|
||||
Shields: 15,
|
||||
Cargo: 0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -50,19 +50,23 @@ func (c *Cache) FleetState(fleetID uuid.UUID) (game.ShipGroupState, *uint, *game
|
||||
}
|
||||
|
||||
// TODO: Hello! Wanna know fleet's speed? Good. Implement & test this func first.
|
||||
func (c *Cache) FleetSpeed(fl game.Fleet) float64 {
|
||||
result := math.MaxFloat64
|
||||
for sg := range c.ShipGroupsIndex() {
|
||||
if c.ShipGroup(sg).FleetID == nil || *c.ShipGroup(sg).FleetID != fl.ID {
|
||||
func (c *Cache) FleetSpeedAndMass(fi int) (float64, float64) {
|
||||
c.validateFleetIndex(fi)
|
||||
speed := math.MaxFloat64
|
||||
mass := 0.
|
||||
for sgi := range c.ShipGroupsIndex() {
|
||||
if c.ShipGroup(sgi).FleetID == nil || *c.ShipGroup(sgi).FleetID != c.g.Fleets[fi].ID {
|
||||
continue
|
||||
}
|
||||
st := c.ShipGroupShipClass(sg)
|
||||
typeSpeed := c.ShipGroup(sg).Speed(st)
|
||||
if typeSpeed < result {
|
||||
result = typeSpeed
|
||||
sg := c.ShipGroup(sgi)
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
typeSpeed := sg.Speed(st)
|
||||
if typeSpeed < speed {
|
||||
speed = typeSpeed
|
||||
}
|
||||
mass += sg.FullMass(st)
|
||||
}
|
||||
return result
|
||||
return speed, mass
|
||||
}
|
||||
|
||||
func (c *Controller) JoinShipGroupToFleet(raceName, fleetName string, group, count uint) error {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
// "github.com/iliadenisov/galaxy/internal/game/battle"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/iliadenisov/galaxy/internal/model/report"
|
||||
)
|
||||
|
||||
func MakeTurn(c *Controller, r Repo) error {
|
||||
@@ -54,13 +54,30 @@ func MakeTurn(c *Controller, r Repo) error {
|
||||
/*** Last steps ***/
|
||||
|
||||
// Store bombings
|
||||
bombingReport := make([]*report.Bombing, len(bombings))
|
||||
if len(bombings) > 0 {
|
||||
if err := r.SaveBombings(c.Cache.g.Turn, bombings); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range bombings {
|
||||
bombingReport[i].Planet = bombings[i].Planet
|
||||
bombingReport[i].PlanetOwnedID = bombings[i].PlanetOwnedID
|
||||
bombingReport[i].Number = bombings[i].Number
|
||||
bombingReport[i].Owner = bombings[i].Owner
|
||||
bombingReport[i].Attacker = bombings[i].Attacker
|
||||
bombingReport[i].Production = bombings[i].Production
|
||||
bombingReport[i].Industry = report.F(bombings[i].Industry.F())
|
||||
bombingReport[i].Population = report.F(bombings[i].Population.F())
|
||||
bombingReport[i].Colonists = report.F(bombings[i].Colonists.F())
|
||||
bombingReport[i].Capital = report.F(bombings[i].Capital.F())
|
||||
bombingReport[i].Material = report.F(bombings[i].Material.F())
|
||||
bombingReport[i].AttackPower = report.F(bombings[i].AttackPower.F())
|
||||
bombingReport[i].Wiped = bombings[i].Wiped
|
||||
}
|
||||
}
|
||||
|
||||
// Store battles
|
||||
battleReport := make([]*report.BattleReport, len(battles))
|
||||
if len(battles) > 0 {
|
||||
battleMeta := make([]game.BattleMeta, len(battles))
|
||||
for i := range battles {
|
||||
@@ -82,15 +99,21 @@ func MakeTurn(c *Controller, r Repo) error {
|
||||
if err := r.SaveBattle(c.Cache.g.Turn, report, &battleMeta[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
battleReport[i] = report
|
||||
}
|
||||
}
|
||||
|
||||
// Remove killed ship groups
|
||||
c.Cache.DeleteKilledShipGroups()
|
||||
|
||||
// TODO: Store game state
|
||||
// Store game state for the new turn and 'current' state as well
|
||||
r.SaveTurn(c.Cache.g.Turn, c.Cache.g)
|
||||
|
||||
// TODO: Store individual reports
|
||||
for ri := range c.Cache.g.Race {
|
||||
_ = ri
|
||||
// c.Cache.GenerateReport(ri)
|
||||
}
|
||||
|
||||
_ = winners
|
||||
// [ ] monitor memory consumption at this point?
|
||||
|
||||
@@ -197,16 +197,38 @@ func (c *Cache) PlanetProductionCapacity(planetNumber uint) float64 {
|
||||
return p.ProductionCapacity() - busyResources
|
||||
}
|
||||
|
||||
// TODO: test upgrade & upgrade cancel
|
||||
func (c *Cache) TurnPlanetProductions() {
|
||||
// cancel upgrade for groups on wiped planets
|
||||
for sgi := range c.ShipGroupsIndex() {
|
||||
sg := c.ShipGroup(sgi)
|
||||
if sg.State() == game.StateUpgrade && c.MustPlanet(sg.Destination).Owner == uuid.Nil {
|
||||
sg.StateUpgrade = nil
|
||||
}
|
||||
}
|
||||
|
||||
for pn := range c.listProducingPlanets() {
|
||||
p := c.MustPlanet(pn)
|
||||
ri := c.RaceIndex(p.Owner)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
// upgrade groups and return to in_orbit state
|
||||
productionAvailable := p.ProductionCapacity()
|
||||
for sg := range c.shipGroupsInUpgrade(p.Number) {
|
||||
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)
|
||||
}
|
||||
productionAvailable -= cost
|
||||
}
|
||||
sg.StateUpgrade = nil
|
||||
}
|
||||
|
||||
switch pt := p.Production.Type; pt {
|
||||
case game.ProductionShip:
|
||||
st := c.MustShipType(ri, *p.Production.SubjectID)
|
||||
if ships := ProduceShip(p, st.EmptyMass()); ships > 0 {
|
||||
if ships := ProduceShip(p, productionAvailable, st.EmptyMass()); ships > 0 {
|
||||
c.createShipsUnsafe(ri, st.ID, p.Number, ships)
|
||||
}
|
||||
case game.ResearchScience:
|
||||
@@ -239,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 || c.g.Map.Planet[i].Production.Type == game.ProductionNone {
|
||||
if c.g.Map.Planet[i].Owner == uuid.Nil {
|
||||
continue
|
||||
}
|
||||
ordered = append(ordered, i)
|
||||
@@ -276,8 +298,7 @@ func (c *Cache) putMaterial(pn uint, v float64) {
|
||||
c.MustPlanet(pn).Mat(v)
|
||||
}
|
||||
|
||||
func ProduceShip(p *game.Planet, shipMass float64) uint {
|
||||
productionAvailable := p.ProductionCapacity()
|
||||
func ProduceShip(p *game.Planet, productionAvailable, shipMass float64) uint {
|
||||
if productionAvailable <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -167,49 +167,47 @@ func TestProduceShips(t *testing.T) {
|
||||
|
||||
func TestProduceShip(t *testing.T) {
|
||||
Drone := game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Drone",
|
||||
Drive: 1,
|
||||
Armament: 0,
|
||||
Weapons: 0,
|
||||
Shields: 0,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "Drone",
|
||||
Drive: 1,
|
||||
Armament: 0,
|
||||
Weapons: 0,
|
||||
Shields: 0,
|
||||
Cargo: 0,
|
||||
}
|
||||
BattleShip := game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "BattleShip",
|
||||
Drive: 25,
|
||||
Armament: 1,
|
||||
Weapons: 30,
|
||||
Shields: 35,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "BattleShip",
|
||||
Drive: 25,
|
||||
Armament: 1,
|
||||
Weapons: 30,
|
||||
Shields: 35,
|
||||
Cargo: 0,
|
||||
}
|
||||
p := controller.NewPlanet(0, "Planet_0", uuid.New(), 1, 1, 1000, 1000, 1000, 10, game.ProductionShip.AsType(uuid.Nil))
|
||||
|
||||
r := controller.ProduceShip(&p, Drone.EmptyMass())
|
||||
r := controller.ProduceShip(&p, p.ProductionCapacity(), Drone.EmptyMass())
|
||||
assert.Equal(t, uint(99), r)
|
||||
assert.InDelta(t, 0.0099, *p.Production.Progress, 0.000001)
|
||||
|
||||
(&p).Production = game.ProductionShip.AsType(uuid.Nil)
|
||||
(&p).Capital = 10.
|
||||
r = controller.ProduceShip(&p, Drone.EmptyMass())
|
||||
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., 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, BattleShip.EmptyMass())
|
||||
r = controller.ProduceShip(&p, p.ProductionCapacity(), BattleShip.EmptyMass())
|
||||
assert.Equal(t, uint(1), r)
|
||||
assert.InDelta(t, 0.1, *p.Production.Progress, 0.001)
|
||||
|
||||
(&p).Production = game.ProductionShip.AsType(uuid.Nil)
|
||||
(&p).Capital = 20.
|
||||
r = controller.ProduceShip(&p, BattleShip.EmptyMass())
|
||||
r = controller.ProduceShip(&p, p.ProductionCapacity(), BattleShip.EmptyMass())
|
||||
assert.Equal(t, uint(1), r)
|
||||
assert.Equal(t, 1./9., *p.Production.Progress)
|
||||
|
||||
// TODO: test with insufficient production capacity
|
||||
}
|
||||
|
||||
func TestListProducingPlanets(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
mr "github.com/iliadenisov/galaxy/internal/model/report"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
)
|
||||
|
||||
func (c *Cache) Report(t uint, battleReports []*mr.BattleReport, bombingReports []*mr.Bombing) iter.Seq[*mr.Report] {
|
||||
report := c.InitReport(t)
|
||||
return func(yield func(*mr.Report) bool) {
|
||||
for i := range c.g.Race {
|
||||
c.ReportRace(i, report, battleReports)
|
||||
if !yield(report) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) InitReport(t uint) *mr.Report {
|
||||
report := &mr.Report{
|
||||
Turn: t,
|
||||
Width: c.g.Map.Width,
|
||||
Height: c.g.Map.Height,
|
||||
PlanetCount: uint32(len(c.g.Map.Planet)),
|
||||
Player: make([]mr.Player, len(c.g.Race)),
|
||||
LocalScience: make([]mr.Science, 0, 10),
|
||||
OtherScience: make([]mr.OtherScience, 0, 10),
|
||||
LocalShipClass: make([]mr.ShipClass, 0, 20),
|
||||
OtherShipClass: make([]mr.OthersShipClass, 0, 50),
|
||||
Battle: make([]uuid.UUID, 0, 10),
|
||||
Bombing: make([]*mr.Bombing, 0, 10),
|
||||
IncomingGroup: make([]mr.IncomingGroup, 0, 10),
|
||||
PlanetGroupsCache: make(map[uint][]int),
|
||||
}
|
||||
|
||||
sumVote, sumPop, sumInd := make(map[int]float64), make(map[int]float64), make(map[int]float64)
|
||||
planets := make(map[int]uint16)
|
||||
for i := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[i]
|
||||
if p.Owner == uuid.Nil {
|
||||
continue
|
||||
}
|
||||
ri := c.RaceIndex(p.Owner)
|
||||
sumPop[ri] += p.Population.F()
|
||||
sumInd[ri] += p.Industry.F()
|
||||
planets[ri] = planets[ri] + 1
|
||||
}
|
||||
|
||||
for ri := range c.g.Race {
|
||||
r := &c.g.Race[ri]
|
||||
rr := &report.Player[ri]
|
||||
|
||||
rr.ID = r.ID
|
||||
rr.Name = r.Name
|
||||
rr.Drive = mr.F(r.TechLevel(game.TechDrive))
|
||||
rr.Weapons = mr.F(r.TechLevel(game.TechWeapons))
|
||||
rr.Shields = mr.F(r.TechLevel(game.TechShields))
|
||||
rr.Cargo = mr.F(r.TechLevel(game.TechCargo))
|
||||
rr.Planets = planets[ri]
|
||||
rr.Population = mr.F(sumPop[ri])
|
||||
rr.Industry = mr.F(sumInd[ri])
|
||||
|
||||
// give voices by race index
|
||||
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
|
||||
dest := &report.Player[vi]
|
||||
dest.Votes = mr.F(sumVote[vi])
|
||||
}
|
||||
|
||||
// collect all orbiting ship groups by planet
|
||||
for sgi := range c.g.ShipGroups {
|
||||
sg := &c.g.ShipGroups[sgi]
|
||||
if sg.State() == game.StateInSpace {
|
||||
continue
|
||||
}
|
||||
report.PlanetGroupsCache[sg.Destination] = append(report.PlanetGroupsCache[sg.Destination], sgi)
|
||||
}
|
||||
}
|
||||
slices.SortFunc(report.Player, func(a, b mr.Player) int { return cmp.Compare(a.Name, b.Name) })
|
||||
|
||||
return report
|
||||
}
|
||||
|
||||
func (c *Cache) ReportRace(ri int, rep *mr.Report, br []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
rep.Race = r.Name
|
||||
rep.RaceID = r.ID
|
||||
|
||||
// votes based on population
|
||||
// TODO: check votes was calculated
|
||||
rep.Votes = mr.F(r.Votes)
|
||||
|
||||
// relations
|
||||
for i := range r.Relations {
|
||||
rii := slices.IndexFunc(rep.Player, func(v mr.Player) bool { return v.ID == r.Relations[i].RaceID })
|
||||
if rii < 0 {
|
||||
panic(fmt.Sprintf("relation race not found, id=%v", r.Relations[i].RaceID))
|
||||
}
|
||||
rep.Player[rii].Relation = r.Relations[i].Relation.String()
|
||||
}
|
||||
// self-relation is undefined
|
||||
|
||||
if i := slices.IndexFunc(rep.Player, func(v mr.Player) bool { return v.ID == r.ID }); i < 0 {
|
||||
panic(fmt.Sprintf("race not found in report, id=%v", r.ID))
|
||||
} else {
|
||||
rep.Player[i].Relation = "-"
|
||||
}
|
||||
|
||||
// sciences
|
||||
c.ReportLocalScience(ri, rep)
|
||||
c.ReportOtherScience(ri, rep)
|
||||
|
||||
// ship classes
|
||||
c.ReportLocalShipClass(ri, rep)
|
||||
c.ReportOtherShipClass(ri, rep, br)
|
||||
}
|
||||
|
||||
func (c *Cache) ReportLocalScience(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.LocalScience)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
slices.SortFunc(rep.LocalScience, func(a, b mr.Science) int { return cmp.Compare(a.Name, b.Name) })
|
||||
}
|
||||
|
||||
func (c *Cache) ReportOtherScience(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.OtherScience)
|
||||
|
||||
i := 0
|
||||
for sg := range c.listShipGroups(ri) {
|
||||
if sg.State() != game.StateInOrbit {
|
||||
continue
|
||||
}
|
||||
p := c.MustPlanet(sg.Destination)
|
||||
if p.Owner == uuid.Nil || p.Owner == r.ID || p.Production.Type != game.ResearchScience {
|
||||
continue
|
||||
}
|
||||
ownerIdx := c.RaceIndex(p.Owner)
|
||||
owner := &c.g.Race[ownerIdx]
|
||||
sc := c.mustScience(ownerIdx, *p.Production.SubjectID)
|
||||
|
||||
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)
|
||||
i++
|
||||
}
|
||||
|
||||
slices.SortFunc(rep.OtherScience, func(a, b mr.OtherScience) int {
|
||||
return cmp.Or(cmp.Compare(a.Race, b.Race), cmp.Compare(a.Name, b.Name))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) ReportLocalShipClass(ri int, report *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
|
||||
clear(report.LocalShipClass)
|
||||
|
||||
i := 0
|
||||
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].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].Mass = mr.F(st.EmptyMass())
|
||||
i++
|
||||
}
|
||||
|
||||
slices.SortFunc(report.LocalShipClass, func(a, b mr.ShipClass) int { return cmp.Compare(a.Name, b.Name) })
|
||||
}
|
||||
|
||||
func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report, br []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.OtherShipClass)
|
||||
|
||||
i := 0
|
||||
|
||||
used := make(map[uuid.UUID]map[string]bool)
|
||||
usedFn := func(ownerID uuid.UUID, className string) bool {
|
||||
if _, ok := used[ownerID]; ok {
|
||||
if _, ok := used[ownerID][className]; ok {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
used[ownerID] = make(map[string]bool)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// add visible ship classes from battles
|
||||
for bi := range br {
|
||||
for si := range br[bi].Ships {
|
||||
g := br[bi].Ships[si]
|
||||
if g.OwnerID == r.ID || usedFn(g.OwnerID, g.ClassName) {
|
||||
continue
|
||||
}
|
||||
used[g.OwnerID][g.ClassName] = true
|
||||
|
||||
sliceIndexValidate(&rep.OtherShipClass, i)
|
||||
rep.OtherShipClass[i].Race = c.g.Race[c.RaceIndex(g.OwnerID)].Name
|
||||
rep.OtherShipClass[i].Name = g.ClassName
|
||||
rep.OtherShipClass[i].Drive = g.DriveTech
|
||||
rep.OtherShipClass[i].Armament = g.ClassArmament
|
||||
rep.OtherShipClass[i].Weapons = g.WeaponsTech
|
||||
rep.OtherShipClass[i].Shields = g.ShieldsTech
|
||||
rep.OtherShipClass[i].Cargo = g.CargoTech
|
||||
rep.OtherShipClass[i].Mass = g.ClassMass
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// add visible ship classes from observable planets
|
||||
for pn := range rep.PlanetGroupsCache {
|
||||
if slices.IndexFunc(rep.PlanetGroupsCache[pn], func(sgi int) bool { return c.ShipGroup(sgi).OwnerID == r.ID }) >= 0 {
|
||||
for _, sgi := range rep.PlanetGroupsCache[pn] {
|
||||
sg := c.ShipGroup(sgi)
|
||||
if sg.OwnerID == r.ID {
|
||||
continue
|
||||
}
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
|
||||
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].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].Mass = mr.F(st.EmptyMass())
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(rep.OtherShipClass, func(a, b mr.OthersShipClass) int {
|
||||
return cmp.Or(cmp.Compare(a.Race, b.Race), cmp.Compare(a.Name, b.Name))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) ReportBattle(ri int, rep *mr.Report, br []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.Battle)
|
||||
|
||||
i := 0
|
||||
for bi := range br {
|
||||
visible := false
|
||||
for k := range br[bi].Races {
|
||||
visible = visible || br[bi].Races[k] == r.ID
|
||||
}
|
||||
if !visible {
|
||||
continue
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.Battle, i)
|
||||
rep.Battle[i] = br[bi].ID
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ReportBombing(ri int, rep *mr.Report, bombing []*mr.Bombing, battle []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.Bombing)
|
||||
|
||||
i := 0
|
||||
for bi := range bombing {
|
||||
pn := bombing[bi].Number
|
||||
visible := bombing[bi].PlanetOwnedID == r.ID // planet may be bombed and wiped
|
||||
for _, sgi := range rep.PlanetGroupsCache[pn] {
|
||||
sg := c.ShipGroup(sgi)
|
||||
visible = visible || (sg.OwnerID == r.ID && sg.Destination == pn)
|
||||
}
|
||||
if !visible {
|
||||
continue
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.Bombing, i)
|
||||
rep.Bombing[i] = bombing[bi]
|
||||
i++
|
||||
}
|
||||
|
||||
slices.SortFunc(rep.Bombing, func(a, b *mr.Bombing) int {
|
||||
return cmp.Or(cmp.Compare(a.Number, b.Number), boolCompare(a.Wiped, b.Wiped))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) ReportIncomingGroup(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.IncomingGroup)
|
||||
|
||||
i := 0
|
||||
for sgi := range c.ShipGroupsIndex() {
|
||||
sg := c.ShipGroup(sgi)
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
if sg.OwnerID == r.ID || sg.State() != game.StateInSpace {
|
||||
continue
|
||||
}
|
||||
p1 := c.MustPlanet(sg.StateInSpace.Origin)
|
||||
p2 := c.MustPlanet(sg.Destination)
|
||||
if p2.Owner != r.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
distance := util.ShortDistance(c.g.Map.Width, c.g.Map.Height, p1.X.F(), p1.Y.F(), p2.X.F(), p2.Y.F())
|
||||
var speed, mass float64
|
||||
if sg.FleetID != nil {
|
||||
speed, mass = c.FleetSpeedAndMass(c.MustFleetIndex(*sg.FleetID))
|
||||
} else {
|
||||
speed, mass = sg.Speed(st), sg.FullMass(st)
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.IncomingGroup, i)
|
||||
rep.IncomingGroup[i].Origin = sg.StateInSpace.Origin
|
||||
rep.IncomingGroup[i].Destination = sg.Destination
|
||||
rep.IncomingGroup[i].Distance = mr.F(distance)
|
||||
rep.IncomingGroup[i].Speed = mr.F(speed)
|
||||
rep.IncomingGroup[i].Mass = mr.F(mass)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ReportLocalPlanet(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.LocalPlanet)
|
||||
|
||||
i := 0
|
||||
for pi := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[pi]
|
||||
if p.Owner != r.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.LocalPlanet, i)
|
||||
rep.LocalPlanet[i].Number = p.Number
|
||||
rep.LocalPlanet[i].X = mr.F(p.X.F())
|
||||
rep.LocalPlanet[i].Y = mr.F(p.Y.F())
|
||||
rep.LocalPlanet[i].Size = mr.F(p.Size.F())
|
||||
rep.LocalPlanet[i].Name = p.Name
|
||||
rep.LocalPlanet[i].Resources = mr.F(p.Resources.F())
|
||||
rep.LocalPlanet[i].Capital = mr.F(p.Capital.F())
|
||||
rep.LocalPlanet[i].Material = mr.F(p.Material.F())
|
||||
rep.LocalPlanet[i].Industry = mr.F(p.Industry.F())
|
||||
rep.LocalPlanet[i].Population = mr.F(p.Population.F())
|
||||
rep.LocalPlanet[i].Colonists = mr.F(p.Colonists.F())
|
||||
rep.LocalPlanet[i].Production = c.PlanetProductionDisplayName(p.Number)
|
||||
rep.LocalPlanet[i].FreeIndustry = mr.F(p.ProductionCapacity())
|
||||
for _, sgi := range rep.PlanetGroupsCache[p.Number] {
|
||||
sg := c.ShipGroup(sgi)
|
||||
if sg.StateUpgrade == nil {
|
||||
break
|
||||
}
|
||||
// between-turn report: ships upgrading on the planet decreases free indistrial potential
|
||||
rep.LocalPlanet[i].FreeIndustry -= mr.F(sg.StateUpgrade.Cost())
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ReportShipProduction(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.ShipProduction)
|
||||
|
||||
i := 0
|
||||
for pi := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[pi]
|
||||
if p.Owner != r.ID || p.Production.Type != game.ProductionShip {
|
||||
continue
|
||||
}
|
||||
st := c.MustShipType(ri, *p.Production.SubjectID)
|
||||
|
||||
sliceIndexValidate(&rep.ShipProduction, i)
|
||||
free := c.PlanetProductionCapacity(p.Number)
|
||||
rep.ShipProduction[pi].Planet = p.Number
|
||||
rep.ShipProduction[pi].Class = st.Name
|
||||
rep.ShipProduction[pi].Cost = mr.F(st.EmptyMass())
|
||||
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)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ReportRoute(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
clear(rep.Route)
|
||||
|
||||
i := 0
|
||||
for pi := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[pi]
|
||||
if p.Owner != r.ID || len(p.Route) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sliceIndexValidate(&rep.Route, i)
|
||||
rep.Route[i].Planet = p.Number
|
||||
// rep.Route[i].Route = make(map[uint]string)
|
||||
for rt, dest := range p.Route {
|
||||
rep.Route[i].Route[dest] = rt.String()
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func sliceIndexValidate[S ~[]E, E any](s *S, i int) {
|
||||
if cap(*s) < i+1 {
|
||||
*s = slices.Grow(*s, 10)
|
||||
}
|
||||
if len(*s) < i+1 {
|
||||
*s = (*s)[:i+1]
|
||||
}
|
||||
}
|
||||
|
||||
func boolCompare(a, b bool) int {
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
if a == false {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package controller_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/model/report"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReportLocalShipClass(t *testing.T) {
|
||||
c, _ := newCache()
|
||||
|
||||
r := &report.Report{}
|
||||
assert.Len(t, r.LocalShipClass, 0)
|
||||
|
||||
c.ReportLocalShipClass(Race_0_idx, r)
|
||||
|
||||
assert.Len(t, r.LocalShipClass, 3)
|
||||
for i := range r.LocalShipClass {
|
||||
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, 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)
|
||||
case Race_0_Gunship:
|
||||
assert.Equal(t, report.F(60.), r.LocalShipClass[i].Drive)
|
||||
assert.Equal(t, uint(3), r.LocalShipClass[i].Armament)
|
||||
assert.Equal(t, report.F(30.), r.LocalShipClass[i].Weapons)
|
||||
assert.Equal(t, report.F(100.), r.LocalShipClass[i].Shields)
|
||||
assert.Equal(t, report.F(0.), r.LocalShipClass[i].Cargo)
|
||||
case Race_0_Freighter:
|
||||
assert.Equal(t, report.F(8.), r.LocalShipClass[i].Drive)
|
||||
assert.Equal(t, uint(0), r.LocalShipClass[i].Armament)
|
||||
assert.Equal(t, report.F(0.), r.LocalShipClass[i].Weapons)
|
||||
assert.Equal(t, report.F(2.), r.LocalShipClass[i].Shields)
|
||||
assert.Equal(t, report.F(10.), r.LocalShipClass[i].Cargo)
|
||||
default:
|
||||
assert.Failf(t, "unexpected ship class", "name=%s", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,14 +44,12 @@ func (c *Cache) CreateScience(ri int, name string, drive, weapons, shileds, carg
|
||||
return e.NewScienceSumValuesError("D=%f W=%f S=%f C=%f sum=%f", drive, weapons, shileds, cargo, sum)
|
||||
}
|
||||
c.g.Race[ri].Sciences = append(c.g.Race[ri].Sciences, game.Science{
|
||||
ID: uuid.New(),
|
||||
ScienceReport: game.ScienceReport{
|
||||
Name: n,
|
||||
Drive: drive,
|
||||
Weapons: weapons,
|
||||
Shields: shileds,
|
||||
Cargo: cargo,
|
||||
},
|
||||
ID: uuid.New(),
|
||||
Name: n,
|
||||
Drive: drive,
|
||||
Weapons: weapons,
|
||||
Shields: shileds,
|
||||
Cargo: cargo,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -30,15 +31,13 @@ func (c *Cache) CreateShipType(ri int, typeName string, drive float64, ammo int,
|
||||
return e.NewEntityTypeNameDuplicateError("ship type %q", c.g.Race[ri].ShipTypes[st].Name)
|
||||
}
|
||||
c.g.Race[ri].ShipTypes = append(c.g.Race[ri].ShipTypes, game.ShipType{
|
||||
ID: uuid.New(),
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: n,
|
||||
Drive: drive,
|
||||
Armament: uint(ammo),
|
||||
Weapons: weapons,
|
||||
Shields: shileds,
|
||||
Cargo: cargo,
|
||||
},
|
||||
ID: uuid.New(),
|
||||
Name: n,
|
||||
Drive: drive,
|
||||
Armament: uint(ammo),
|
||||
Weapons: weapons,
|
||||
Shields: shileds,
|
||||
Cargo: cargo,
|
||||
})
|
||||
c.invalidateShipGroupCache()
|
||||
c.invalidateFleetCache()
|
||||
@@ -155,6 +154,16 @@ func (c *Cache) ShipTypes(ri int) []*game.ShipType {
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Cache) ListShipTypes(ri int) iter.Seq[*game.ShipType] {
|
||||
return func(yield func(*game.ShipType) bool) {
|
||||
for i := range c.g.Race[ri].ShipTypes {
|
||||
if !yield(&c.g.Race[ri].ShipTypes[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ShipClass(ri int, name string) (*game.ShipType, int, bool) {
|
||||
i := slices.IndexFunc(c.g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.Name == name })
|
||||
if i < 0 {
|
||||
|
||||
@@ -576,10 +576,12 @@ func (c *Cache) listShipGroups(ri int) iter.Seq[*game.ShipGroup] {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: order upgradable groups by cost descending, describe in Rules
|
||||
func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup] {
|
||||
return func(yield func(*game.ShipGroup) bool) {
|
||||
for sg := range c.g.ShipGroups {
|
||||
if c.g.ShipGroups[sg].Destination == planetNumber && c.g.ShipGroups[sg].State() == game.StateUpgrade {
|
||||
// number checked for further sanity after battles
|
||||
if c.g.ShipGroups[sg].Number > 0 && c.g.ShipGroups[sg].Destination == planetNumber && c.g.ShipGroups[sg].State() == game.StateUpgrade {
|
||||
if !yield(&c.g.ShipGroups[sg]) {
|
||||
break
|
||||
}
|
||||
@@ -588,9 +590,9 @@ func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) unsafeDeleteShipGroup(i int) {
|
||||
c.validateShipGroupIndex(i)
|
||||
c.g.ShipGroups = append(c.g.ShipGroups[:i], c.g.ShipGroups[i+1:]...)
|
||||
func (c *Cache) unsafeDeleteShipGroup(sgi int) {
|
||||
c.validateShipGroupIndex(sgi)
|
||||
c.g.ShipGroups = append(c.g.ShipGroups[:sgi], c.g.ShipGroups[sgi+1:]...)
|
||||
c.invalidateShipGroupCache()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func (c *Cache) MoveShipGroups() {
|
||||
|
||||
if sg.FleetID != nil {
|
||||
fi := c.MustFleetIndex(*sg.FleetID)
|
||||
delta := c.FleetSpeed(c.g.Fleets[fi])
|
||||
delta, _ := c.FleetSpeedAndMass(fi)
|
||||
for fgi := range c.fleetGroupIds(c.RaceIndex(sg.OwnerID), c.MustFleetIndex(*sg.FleetID)) {
|
||||
c.moveShipGroup(fgi, delta)
|
||||
moved[fgi] = true
|
||||
|
||||
@@ -21,14 +21,15 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi
|
||||
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
||||
}
|
||||
st := c.ShipGroupShipClass(sgi)
|
||||
sg := c.ShipGroup(sgi)
|
||||
|
||||
if s := c.ShipGroup(sgi).State(); s != game.StateInOrbit && s != game.StateUpgrade {
|
||||
if s := sg.State(); s != game.StateInOrbit { // && s != game.StateUpgrade
|
||||
return e.NewShipsBusyError()
|
||||
}
|
||||
|
||||
pl := c.MustPlanet(c.ShipGroup(sgi).Destination)
|
||||
if pl.Owner != uuid.Nil && pl.Owner != c.g.Race[ri].ID {
|
||||
return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", pl.Number, groupIndex)
|
||||
p := c.MustPlanet(sg.Destination)
|
||||
if p.Owner != uuid.Nil && p.Owner != c.g.Race[ri].ID {
|
||||
return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", p.Number, groupIndex)
|
||||
}
|
||||
|
||||
upgradeValidTech := map[string]game.Tech{
|
||||
@@ -65,26 +66,25 @@ 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), c.ShipGroup(sgi).TechLevel(tech).F(), limitLevel)
|
||||
targetLevel[tech] = game.FutureUpgradeLevel(c.g.Race[ri].TechLevel(tech), sg.TechLevel(tech).F(), limitLevel)
|
||||
} else {
|
||||
targetLevel[tech] = game.CurrentUpgradingLevel(c.g.ShipGroups[sgi], tech)
|
||||
targetLevel[tech] = game.CurrentUpgradingLevel(sg, tech)
|
||||
}
|
||||
sumLevels += targetLevel[tech]
|
||||
}
|
||||
|
||||
productionCapacity := c.PlanetProductionCapacity(pl.Number)
|
||||
if c.ShipGroup(sgi).State() == game.StateUpgrade {
|
||||
// to calculate actual capacity we must "compensate" upgrade cost of selected group, if it is in upgrade state
|
||||
// TODO: this is not tested
|
||||
productionCapacity += c.ShipGroup(sgi).StateUpgrade.Cost()
|
||||
}
|
||||
uc := game.GroupUpgradeCost(*(c.ShipGroup(sgi)), *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo])
|
||||
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])
|
||||
costForShip := uc.UpgradeCost(1)
|
||||
if costForShip == 0 {
|
||||
return e.NewUpgradeShipsAlreadyUpToDateError("%#v", targetLevel)
|
||||
}
|
||||
|
||||
shipsToUpgrade := c.ShipGroup(sgi).Number
|
||||
shipsToUpgrade := sg.Number
|
||||
// НЕ БОЛЕЕ УКАЗАННОГО
|
||||
if limitShips > 0 && shipsToUpgrade > limitShips {
|
||||
shipsToUpgrade = limitShips
|
||||
@@ -118,16 +118,16 @@ func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShi
|
||||
}
|
||||
|
||||
// sanity check
|
||||
uc = game.GroupUpgradeCost(*(c.ShipGroup(sgi)), *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo])
|
||||
uc = game.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)
|
||||
}
|
||||
|
||||
// break group if needed
|
||||
if maxUpgradableShips < c.ShipGroup(sgi).Number {
|
||||
if c.ShipGroup(sgi).State() == game.StateUpgrade {
|
||||
return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", c.ShipGroup(sgi).Number, maxUpgradableShips)
|
||||
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 {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package game
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type Bombing struct {
|
||||
ID uuid.UUID `json:"-"`
|
||||
PlanetOwnedID uuid.UUID `json:"-"` // for the report filtering
|
||||
Planet string `json:"name"`
|
||||
Number uint `json:"number"`
|
||||
Owner string `json:"owner"`
|
||||
Attacker string `json:"attacker"`
|
||||
Production string `json:"production"`
|
||||
Industry Float `json:"industry"` // I - Промышленность
|
||||
Population Float `json:"population"` // P - Население
|
||||
Colonists Float `json:"colonists"` // COL C - Количество колонистов
|
||||
Capital Float `json:"capital"` // CAP $ - Запасы промышленности
|
||||
Material Float `json:"material"` // MAT M - Запасы ресурсов / сырья
|
||||
AttackPower Float `json:"attack"`
|
||||
Wiped bool `json:"wiped"`
|
||||
}
|
||||
@@ -6,9 +6,22 @@ import (
|
||||
"maps"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/report"
|
||||
)
|
||||
|
||||
type Float float64
|
||||
|
||||
func F(v float64) Float {
|
||||
return Float(v)
|
||||
}
|
||||
|
||||
func (f Float) Add(v float64) Float {
|
||||
return F(f.F() + v)
|
||||
}
|
||||
|
||||
func (f Float) F() float64 {
|
||||
return float64(f)
|
||||
}
|
||||
|
||||
type TechSet map[Tech]float64
|
||||
|
||||
func (ts TechSet) Value(t Tech) float64 {
|
||||
@@ -25,6 +38,7 @@ func (ts TechSet) Set(t Tech, v float64) TechSet {
|
||||
return m
|
||||
}
|
||||
|
||||
// TODO: turn's incremental Version
|
||||
type Game struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Turn uint `json:"turn"`
|
||||
@@ -36,8 +50,8 @@ type Game struct {
|
||||
}
|
||||
|
||||
type GameMeta struct {
|
||||
Battles []BattleMeta `json:"battles,omitempty"`
|
||||
Bombings []report.BombingPlanetReport `json:"bombings,omitempty"`
|
||||
Battles []BattleMeta `json:"battles,omitempty"`
|
||||
Bombings []Bombing `json:"bombings,omitempty"`
|
||||
}
|
||||
|
||||
type BattleMeta struct {
|
||||
@@ -66,10 +80,10 @@ func (g *Game) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, g)
|
||||
}
|
||||
|
||||
func (b GameMeta) MarshalBinary() (data []byte, err error) {
|
||||
return json.Marshal(&b)
|
||||
func (gm GameMeta) MarshalBinary() (data []byte, err error) {
|
||||
return json.Marshal(&gm)
|
||||
}
|
||||
|
||||
func (b *GameMeta) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, b)
|
||||
func (gm *GameMeta) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, gm)
|
||||
}
|
||||
|
||||
@@ -100,21 +100,17 @@ func (t Tech) String() string {
|
||||
}
|
||||
|
||||
type ShipGroup struct {
|
||||
Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered)
|
||||
OwnerID uuid.UUID `json:"ownerId"` // Race link
|
||||
TypeID uuid.UUID `json:"typeId"` // ShipType link
|
||||
FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link
|
||||
Number uint `json:"number"` // Number (quantity) ships of specific ShipType
|
||||
|
||||
CargoType *CargoType `json:"loadType,omitempty"`
|
||||
Load Float `json:"load"` // Cargo loaded - "Масса груза"
|
||||
|
||||
Tech TechSet `json:"tech"`
|
||||
|
||||
// TODO: TEST: Destination, Origin, Range
|
||||
Destination uint `json:"destination"`
|
||||
StateInSpace *InSpace `json:"stateInSpace,omitempty"`
|
||||
StateUpgrade *InUpgrade `json:"stateUpgrade,omitempty"`
|
||||
Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered)
|
||||
OwnerID uuid.UUID `json:"ownerId"` // Race link
|
||||
TypeID uuid.UUID `json:"typeId"` // ShipType link
|
||||
FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link
|
||||
Number uint `json:"number"` // Number (quantity) ships of specific ShipType
|
||||
CargoType *CargoType `json:"loadType,omitempty"` //
|
||||
Load Float `json:"load"` // Cargo loaded - "Масса груза"
|
||||
Tech TechSet `json:"tech"` //
|
||||
Destination uint `json:"destination"` // TODO: TEST: Destination, Origin, Range
|
||||
StateInSpace *InSpace `json:"stateInSpace,omitempty"` //
|
||||
StateUpgrade *InUpgrade `json:"stateUpgrade,omitempty"` //
|
||||
}
|
||||
|
||||
func (sg ShipGroup) TechLevel(t Tech) Float {
|
||||
|
||||
@@ -13,13 +13,11 @@ import (
|
||||
func TestCargoCapacity(t *testing.T) {
|
||||
test := func(cargoSize float64, expectCapacity float64) {
|
||||
ship := game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Drive: 1,
|
||||
Armament: 1,
|
||||
Weapons: 1,
|
||||
Shields: 1,
|
||||
Cargo: cargoSize,
|
||||
},
|
||||
Drive: 1,
|
||||
Armament: 1,
|
||||
Weapons: 1,
|
||||
Shields: 1,
|
||||
Cargo: cargoSize,
|
||||
}
|
||||
sg := game.ShipGroup{
|
||||
Number: 1,
|
||||
@@ -41,14 +39,12 @@ func TestCargoCapacity(t *testing.T) {
|
||||
|
||||
func TestCarryingAndFullMass(t *testing.T) {
|
||||
Freighter := &game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Freighter",
|
||||
Drive: 8,
|
||||
Armament: 0,
|
||||
Weapons: 0,
|
||||
Shields: 2,
|
||||
Cargo: 10,
|
||||
},
|
||||
Name: "Freighter",
|
||||
Drive: 8,
|
||||
Armament: 0,
|
||||
Weapons: 0,
|
||||
Shields: 2,
|
||||
Cargo: 10,
|
||||
}
|
||||
sg := &game.ShipGroup{
|
||||
Number: 1,
|
||||
@@ -75,14 +71,12 @@ func TestCarryingAndFullMass(t *testing.T) {
|
||||
|
||||
func TestSpeed(t *testing.T) {
|
||||
Freighter := &game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Freighter",
|
||||
Drive: 8,
|
||||
Armament: 0,
|
||||
Weapons: 0,
|
||||
Shields: 2,
|
||||
Cargo: 10,
|
||||
},
|
||||
Name: "Freighter",
|
||||
Drive: 8,
|
||||
Armament: 0,
|
||||
Weapons: 0,
|
||||
Shields: 2,
|
||||
Cargo: 10,
|
||||
}
|
||||
sg := &game.ShipGroup{
|
||||
Number: 1,
|
||||
@@ -106,14 +100,12 @@ func TestSpeed(t *testing.T) {
|
||||
|
||||
func TestBombingPower(t *testing.T) {
|
||||
BattleStation := game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Battle_Station",
|
||||
Drive: 60.0,
|
||||
Armament: 3,
|
||||
Weapons: 30.0,
|
||||
Shields: 100.0,
|
||||
Cargo: 0.0,
|
||||
},
|
||||
Name: "Battle_Station",
|
||||
Drive: 60.0,
|
||||
Armament: 3,
|
||||
Weapons: 30.0,
|
||||
Shields: 100.0,
|
||||
Cargo: 0.0,
|
||||
}
|
||||
sg := game.ShipGroup{
|
||||
Number: 1,
|
||||
@@ -145,13 +137,11 @@ func TestDriveEffective(t *testing.T) {
|
||||
}
|
||||
for i := range tc {
|
||||
someShip := game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Drive: tc[i].driveShipType,
|
||||
Armament: rand.UintN(30) + 1,
|
||||
Weapons: rand.Float64()*30 + 1,
|
||||
Shields: rand.Float64()*100 + 1,
|
||||
Cargo: rand.Float64()*20 + 1,
|
||||
},
|
||||
Drive: tc[i].driveShipType,
|
||||
Armament: rand.UintN(30) + 1,
|
||||
Weapons: rand.Float64()*30 + 1,
|
||||
Shields: rand.Float64()*100 + 1,
|
||||
Cargo: rand.Float64()*20 + 1,
|
||||
}
|
||||
sg := game.ShipGroup{
|
||||
Number: rand.UintN(4) + 1,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
@@ -12,7 +11,7 @@ type UpgradeCalc struct {
|
||||
|
||||
func (uc UpgradeCalc) UpgradeCost(ships uint) float64 {
|
||||
var sum float64
|
||||
for v := range maps.Values(uc.Cost) {
|
||||
for _, v := range uc.Cost {
|
||||
sum += v
|
||||
}
|
||||
return sum * float64(ships)
|
||||
@@ -29,7 +28,7 @@ func BlockUpgradeCost(blockMass, currentBlockTech, targetBlockTech float64) floa
|
||||
return (1 - currentBlockTech/targetBlockTech) * 10 * blockMass
|
||||
}
|
||||
|
||||
func GroupUpgradeCost(sg ShipGroup, st ShipType, drive, weapons, shields, cargo float64) UpgradeCalc {
|
||||
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)
|
||||
@@ -46,7 +45,7 @@ func GroupUpgradeCost(sg ShipGroup, st ShipType, drive, weapons, shields, cargo
|
||||
return *uc
|
||||
}
|
||||
|
||||
func CurrentUpgradingLevel(sg ShipGroup, tech Tech) float64 {
|
||||
func CurrentUpgradingLevel(sg *ShipGroup, tech Tech) float64 {
|
||||
if sg.StateUpgrade == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -9,14 +9,12 @@ import (
|
||||
|
||||
var (
|
||||
Cruiser = game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Cruiser",
|
||||
Drive: 15,
|
||||
Armament: 1,
|
||||
Weapons: 15,
|
||||
Shields: 15,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "Cruiser",
|
||||
Drive: 15,
|
||||
Armament: 1,
|
||||
Weapons: 15,
|
||||
Shields: 15,
|
||||
Cargo: 0,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -27,7 +25,7 @@ func TestBlockUpgradeCost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGroupUpgradeCost(t *testing.T) {
|
||||
sg := game.ShipGroup{
|
||||
sg := &game.ShipGroup{
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1.0,
|
||||
game.TechWeapons: 1.0,
|
||||
@@ -40,7 +38,7 @@ func TestGroupUpgradeCost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpgradeMaxShips(t *testing.T) {
|
||||
sg := game.ShipGroup{
|
||||
sg := &game.ShipGroup{
|
||||
Tech: map[game.Tech]float64{
|
||||
game.TechDrive: 1.0,
|
||||
game.TechWeapons: 1.0,
|
||||
@@ -57,26 +55,26 @@ 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))
|
||||
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))
|
||||
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))
|
||||
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) {
|
||||
|
||||
@@ -27,9 +27,9 @@ type PlanetReport struct {
|
||||
Population Float `json:"population"` // P - Население
|
||||
Colonists Float `json:"colonists"` // COL C - Количество колонистов
|
||||
Production Production `json:"production"` // TODO: internal/report format
|
||||
// Параметр "L" - Свободный производственный потенциал
|
||||
}
|
||||
|
||||
// TODO: unwrap in one struct
|
||||
type Planet struct {
|
||||
Owner uuid.UUID `json:"owner"` // FIXME: nil value when no owner
|
||||
Route map[RouteType]uint `json:"route"`
|
||||
|
||||
@@ -20,6 +20,10 @@ type Race struct {
|
||||
|
||||
type Relation string
|
||||
|
||||
func (r Relation) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
const (
|
||||
RelationWar Relation = "War"
|
||||
RelationPeace Relation = "Peace"
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package game
|
||||
|
||||
type Report struct {
|
||||
Width, Height uint32
|
||||
PlanetCount uint32 // do we need that?
|
||||
PlayersLeft uint32 // do we need that?
|
||||
|
||||
Votes float64
|
||||
VoteFor string
|
||||
|
||||
Statuses []PlayerStatus
|
||||
|
||||
Sciences []ScienceReport
|
||||
ForeignSciences []ScienceReportForeign
|
||||
|
||||
ShipTypes []ShipTypeReport
|
||||
ForeignShipTypes []ShipTypeReportForeign
|
||||
|
||||
Battles []any // TODO: tbd
|
||||
|
||||
Bombings []any // TODO: tbd
|
||||
|
||||
IncomingGroups []IncomingGroup
|
||||
|
||||
Planets []PlanetReport
|
||||
ForeignPlanets []PlanetReportForeign
|
||||
UninhabitedPlanets []UninhabitedPlanet
|
||||
UnidentifiedPlanets []UnidentifiedPlanet
|
||||
|
||||
ShipsInProduction []any // TODO: tbd
|
||||
|
||||
Routes []any // TODO: tbd
|
||||
|
||||
Fleets []any // TODO: tbd
|
||||
|
||||
ShipGroups []any // TODO: tbd
|
||||
|
||||
ForeignShipGroups []any // TODO: tbd
|
||||
|
||||
UnidentifiedGroups []any // TODO: tbd
|
||||
}
|
||||
|
||||
type IncomingGroup struct {
|
||||
SourcePlanetNumber uint
|
||||
TargetPlanetNumber uint
|
||||
Distance float64
|
||||
Speed float64
|
||||
Mass float64
|
||||
}
|
||||
|
||||
type ReportRelation struct {
|
||||
RaceName string
|
||||
Relation string
|
||||
}
|
||||
|
||||
type PlayerStatus struct {
|
||||
Name string
|
||||
Drive float64 `json:"drive"`
|
||||
Weapons float64 `json:"weapons"`
|
||||
Shields float64 `json:"shields"`
|
||||
Cargo float64 `json:"cargo"`
|
||||
Population float64
|
||||
Industry float64
|
||||
Planets uint16
|
||||
Relation ReportRelation
|
||||
Votes float64
|
||||
}
|
||||
@@ -5,19 +5,10 @@ import (
|
||||
)
|
||||
|
||||
type Science struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
ScienceReport
|
||||
}
|
||||
|
||||
type ScienceReportForeign struct {
|
||||
RaceName string
|
||||
ScienceReport
|
||||
}
|
||||
|
||||
type ScienceReport struct {
|
||||
Name string `json:"name"`
|
||||
Drive float64 `json:"drive"`
|
||||
Weapons float64 `json:"weapons"`
|
||||
Shields float64 `json:"shields"`
|
||||
Cargo float64 `json:"cargo"`
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -4,23 +4,14 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ShipTypeReport struct {
|
||||
Name string `json:"name"`
|
||||
Drive float64 `json:"drive"`
|
||||
Armament uint `json:"armament"`
|
||||
Weapons float64 `json:"weapons"`
|
||||
Shields float64 `json:"shields"`
|
||||
Cargo float64 `json:"cargo"`
|
||||
}
|
||||
|
||||
type ShipType struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
ShipTypeReport
|
||||
}
|
||||
|
||||
type ShipTypeReportForeign struct {
|
||||
RaceName string
|
||||
ShipTypeReport
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Drive float64 `json:"drive"`
|
||||
Armament uint `json:"armament"`
|
||||
Weapons float64 `json:"weapons"`
|
||||
Shields float64 `json:"shields"`
|
||||
Cargo float64 `json:"cargo"`
|
||||
}
|
||||
|
||||
func (st ShipType) Equal(o ShipType) bool {
|
||||
|
||||
@@ -9,38 +9,32 @@ import (
|
||||
|
||||
func TestEmptyMass(t *testing.T) {
|
||||
Freighter := game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Freighter",
|
||||
Drive: 8,
|
||||
Armament: 0,
|
||||
Weapons: 0,
|
||||
Shields: 2,
|
||||
Cargo: 10,
|
||||
},
|
||||
Name: "Freighter",
|
||||
Drive: 8,
|
||||
Armament: 0,
|
||||
Weapons: 0,
|
||||
Shields: 2,
|
||||
Cargo: 10,
|
||||
}
|
||||
assert.Equal(t, 20., Freighter.EmptyMass())
|
||||
|
||||
Gunship := game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Gunship",
|
||||
Drive: 4,
|
||||
Armament: 2,
|
||||
Weapons: 2,
|
||||
Shields: 4,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "Gunship",
|
||||
Drive: 4,
|
||||
Armament: 2,
|
||||
Weapons: 2,
|
||||
Shields: 4,
|
||||
Cargo: 0,
|
||||
}
|
||||
assert.Equal(t, 11., Gunship.EmptyMass())
|
||||
|
||||
Cruiser := game.ShipType{
|
||||
ShipTypeReport: game.ShipTypeReport{
|
||||
Name: "Cruiser",
|
||||
Drive: 15,
|
||||
Armament: 1,
|
||||
Weapons: 15,
|
||||
Shields: 15,
|
||||
Cargo: 0,
|
||||
},
|
||||
Name: "Cruiser",
|
||||
Drive: 15,
|
||||
Armament: 1,
|
||||
Weapons: 15,
|
||||
Shields: 15,
|
||||
Cargo: 0,
|
||||
}
|
||||
assert.Equal(t, 45., Cruiser.EmptyMass())
|
||||
}
|
||||
|
||||
@@ -7,27 +7,30 @@ import (
|
||||
)
|
||||
|
||||
type BattleReport struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Turn uint `json:"turn"`
|
||||
Planet uint `json:"planet"`
|
||||
PlanetName string `json:"planet_name"`
|
||||
Races map[int]uuid.UUID `json:"races"`
|
||||
Ships map[int]BattleReportGroup `json:"ships"`
|
||||
Protocol []BattleActionReport `json:"protocol"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
Planet uint `json:"planet"`
|
||||
PlanetName string `json:"planetName"`
|
||||
// PlanetOwnedID uuid.UUID `json:"-"` // TODO: need this? for make report: bombings: initial owher of a planet
|
||||
Races map[int]uuid.UUID `json:"races"`
|
||||
Ships map[int]BattleReportGroup `json:"ships"`
|
||||
Protocol []BattleActionReport `json:"protocol"`
|
||||
}
|
||||
|
||||
type BattleReportGroup struct {
|
||||
OwnerID uuid.UUID `json:"ownerId"`
|
||||
InBattle bool `json:"inBattle"`
|
||||
Number uint `json:"num"`
|
||||
NumberLeft uint `json:"numLeft"`
|
||||
ClassName string `json:"className"`
|
||||
LoadType string `json:"loadType"`
|
||||
LoadQuantity Float `json:"loadQuantity"`
|
||||
Drive Float `json:"drive"`
|
||||
Weapons Float `json:"wwapons"`
|
||||
Shields Float `json:"shields"`
|
||||
Cargo Float `json:"cargo"`
|
||||
OwnerID uuid.UUID `json:"-"` // make report: visible ship class
|
||||
InBattle bool `json:"inBattle"`
|
||||
Number uint `json:"num"`
|
||||
NumberLeft uint `json:"numLeft"`
|
||||
ClassArmament uint `json:"-"` // make report: visible ship class
|
||||
ClassMass Float `json:"-"` // make report: visible ship class
|
||||
LoadQuantity Float `json:"loadQuantity"`
|
||||
DriveTech Float `json:"drive"`
|
||||
WeaponsTech Float `json:"wwapons"`
|
||||
ShieldsTech Float `json:"shields"`
|
||||
CargoTech Float `json:"cargo"`
|
||||
Race string `json:"race"`
|
||||
ClassName string `json:"className"`
|
||||
LoadType string `json:"loadType"`
|
||||
}
|
||||
|
||||
type BattleActionReport struct {
|
||||
|
||||
@@ -2,18 +2,18 @@ package report
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
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 Float `json:"industry"` // I - Промышленность
|
||||
Population Float `json:"population"` // P - Население
|
||||
Colonists Float `json:"colonists"` // COL C - Количество колонистов
|
||||
Capital Float `json:"capital"` // CAP $ - Запасы промышленности
|
||||
Material Float `json:"material"` // MAT M - Запасы ресурсов / сырья
|
||||
AttackPower Float `json:"attack"`
|
||||
Wiped bool `json:"wiped"`
|
||||
type Bombing struct {
|
||||
PlanetOwnedID uuid.UUID `json:"-"` // make report: filter by planet's owner before bombing
|
||||
Number uint `json:"planet"`
|
||||
Planet string `json:"planetName"`
|
||||
Owner string `json:"owner"`
|
||||
Attacker string `json:"attacker"`
|
||||
Production string `json:"production"`
|
||||
Industry Float `json:"industry"` // I - Промышленность
|
||||
Population Float `json:"population"` // P - Население
|
||||
Colonists Float `json:"colonists"` // COL C - Количество колонистов
|
||||
Capital Float `json:"capital"` // CAP $ - Запасы промышленности
|
||||
Material Float `json:"material"` // MAT M - Запасы ресурсов / сырья
|
||||
AttackPower Float `json:"attack"`
|
||||
Wiped bool `json:"wiped"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package report
|
||||
|
||||
type OtherPlanet struct {
|
||||
Owner string `json:"owner"`
|
||||
LocalPlanet
|
||||
}
|
||||
|
||||
type LocalPlanet struct {
|
||||
UninhabitedPlanet
|
||||
Industry Float `json:"industry"` // I - Промышленность
|
||||
Population Float `json:"population"` // P - Население
|
||||
Colonists Float `json:"colonists"` // COL C - Количество колонистов
|
||||
Production string `json:"production"`
|
||||
FreeIndustry Float `json:"freeInductry"` // Параметр "L" - Свободный производственный потенциал
|
||||
}
|
||||
|
||||
type UninhabitedPlanet struct {
|
||||
UnidentifiedPlanet
|
||||
Size Float `json:"size"`
|
||||
Name string `json:"name"`
|
||||
Resources Float `json:"resources"` // R - Ресурсы
|
||||
Capital Float `json:"capital"` // CAP $ - Запасы промышленности
|
||||
Material Float `json:"material"` // MAT M - Запасы ресурсов / сырьё
|
||||
}
|
||||
|
||||
type UnidentifiedPlanet struct {
|
||||
X Float `json:"x"`
|
||||
Y Float `json:"y"`
|
||||
Number uint `json:"number"`
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/google/uuid"
|
||||
n "github.com/iliadenisov/galaxy/internal/number"
|
||||
)
|
||||
|
||||
type Float float64
|
||||
|
||||
func F(v float64) Float {
|
||||
return Float(n.Fixed3(v))
|
||||
}
|
||||
|
||||
type Report struct {
|
||||
Version uint `json:"version"`
|
||||
Turn uint `json:"turn"`
|
||||
Width uint32 `json:"mapWidth"`
|
||||
Height uint32 `json:"mapHeight"`
|
||||
PlanetCount uint32 `json:"mapPlanets"`
|
||||
Race string `json:"race"`
|
||||
RaceID uuid.UUID `json:"-"`
|
||||
Votes Float `json:"votes"`
|
||||
VoteFor string `json:"voteFor"`
|
||||
Player []Player `json:"player"`
|
||||
LocalScience []Science `json:"localScience,omitempty"`
|
||||
OtherScience []OtherScience `json:"otherScience,omitempty"`
|
||||
LocalShipClass []ShipClass `json:"localShipClass,omitempty"`
|
||||
OtherShipClass []OthersShipClass `json:"otherShipClass,omitempty"`
|
||||
Battle []uuid.UUID `json:"battle,omitempty"`
|
||||
Bombing []*Bombing `json:"bombing,omitempty"`
|
||||
IncomingGroup []IncomingGroup `json:"incomingGroup,omitempty"`
|
||||
LocalPlanet []LocalPlanet `json:"localPlanet,omitempty"`
|
||||
ShipProduction []ShipProduction `json:"shipProduction,omitempty"`
|
||||
Route []Route `json:"route,omitempty"`
|
||||
|
||||
OtherPlanet []OtherPlanet
|
||||
UninhabitedPlanet []UninhabitedPlanet
|
||||
UnidentifiedPlanet []UnidentifiedPlanet
|
||||
Fleet []any // TODO: tbd
|
||||
LocalShipGroup []any // TODO: tbd
|
||||
OtherShipGroup []any // TODO: tbd
|
||||
UnidentifiedGroups []any // TODO: tbd
|
||||
|
||||
PlanetGroupsCache map[uint][]int `json:"-"`
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
Planet uint `json:"planet"`
|
||||
Route map[uint]string `json:"route"`
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
ID uuid.UUID `json:"-"`
|
||||
Name string `json:"name"`
|
||||
Drive Float `json:"drive"`
|
||||
Weapons Float `json:"weapons"`
|
||||
Shields Float `json:"shields"`
|
||||
Cargo Float `json:"cargo"`
|
||||
Population Float `json:"population"`
|
||||
Industry Float `json:"industry"`
|
||||
Planets uint16 `json:"planets"`
|
||||
Relation string `json:"relation"`
|
||||
Votes Float `json:"votes"`
|
||||
}
|
||||
|
||||
func (r Report) MarshalBinary() (data []byte, err error) {
|
||||
return json.Marshal(&r)
|
||||
}
|
||||
|
||||
func (r *Report) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, r)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package report
|
||||
|
||||
type Science struct {
|
||||
Name string `json:"name"`
|
||||
Drive Float `json:"drive"`
|
||||
Weapons Float `json:"weapons"`
|
||||
Shields Float `json:"shields"`
|
||||
Cargo Float `json:"cargo"`
|
||||
}
|
||||
|
||||
type OtherScience struct {
|
||||
Race string `json:"race"`
|
||||
Science
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package report
|
||||
|
||||
type ShipClass struct {
|
||||
Name string `json:"name"`
|
||||
Drive Float `json:"drive"`
|
||||
Armament uint `json:"armament"`
|
||||
Weapons Float `json:"weapons"`
|
||||
Shields Float `json:"shields"`
|
||||
Cargo Float `json:"cargo"`
|
||||
Mass Float `json:"mass"`
|
||||
}
|
||||
|
||||
type OthersShipClass struct {
|
||||
Race string `json:"race"`
|
||||
ShipClass
|
||||
}
|
||||
|
||||
type ShipProduction struct {
|
||||
Planet uint `json:"planet"` // Галактический номер планеты
|
||||
Class string `json:"class"` // Наименование типа строящегося корабля
|
||||
Cost Float `json:"cost"` // Стоимость постройки одного такого корабля (в производственных ед.) без учета расходов на добычу сырья
|
||||
Wasted Float `json:"wasted"` // Сколько производственных единиц уже было затрачено на постройку этого корабля (уже учитывая производство сырья)
|
||||
Free Float `json:"free"` // Свободный производственный потенциал
|
||||
}
|
||||
|
||||
type IncomingGroup struct {
|
||||
Origin uint `json:"origin"`
|
||||
Destination uint `json:"destination"`
|
||||
Distance Float `json:"distance"`
|
||||
Speed Float `json:"speed"`
|
||||
Mass Float `json:"mass"`
|
||||
}
|
||||
@@ -141,12 +141,14 @@ func saveBattle(s Storage, t uint, b *report.BattleReport) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *repo) SaveBombings(t uint, b []report.BombingPlanetReport) error {
|
||||
func (r *repo) SaveBombings(t uint, b []*game.Bombing) error {
|
||||
meta, err := loadMeta(r.s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta.Bombings = b
|
||||
for i := range b {
|
||||
meta.Bombings = append(meta.Bombings, *b[i])
|
||||
}
|
||||
return saveMeta(r.s, t, meta)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user