support multi-module (#4)

* add multimodule
* re-package modules
This commit is contained in:
Ilia Denisov
2026-02-22 08:57:19 +02:00
committed by GitHub
parent 9e36d7151e
commit 8f982278d2
132 changed files with 317 additions and 191 deletions
+20
View File
@@ -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"`
}
+9
View File
@@ -0,0 +1,9 @@
package game
import "github.com/google/uuid"
type Fleet struct {
ID uuid.UUID `json:"id"`
OwnerID uuid.UUID `json:"ownerId"`
Name string `json:"name"`
}
+94
View File
@@ -0,0 +1,94 @@
package game
import (
"encoding/json"
"fmt"
"maps"
"galaxy/util"
"github.com/google/uuid"
)
type Float float64
func F(v float64) Float {
return Float(v)
}
func (f Float) Add(v float64) Float {
return f + F(v)
}
func (f Float) F() float64 {
return util.Fixed12(float64(f))
}
type TechSet map[Tech]Float
func (ts TechSet) Value(t Tech) float64 {
if v, ok := ts[t]; ok {
return v.F()
} else {
panic(fmt.Sprintf("TechSet: Value: %s's value not set", t.String()))
}
}
func (ts TechSet) Set(t Tech, v float64) TechSet {
m := maps.Clone(ts)
m[t] = F(v)
return m
}
func NewTechSet() TechSet {
return TechSet{
TechDrive: 1.,
TechWeapons: 1.,
TechShields: 1.,
TechCargo: 1.,
}
}
type Game struct {
ID uuid.UUID `json:"id"`
Turn uint `json:"turn"`
Stage uint `json:"stage"`
Map Map `json:"map"`
Race []Race `json:"races"`
Votes Float `json:"votes"`
ShipGroups []ShipGroup `json:"shipGroup,omitempty"`
Fleets []Fleet `json:"fleet,omitempty"`
Winner []uuid.UUID `json:"winner,omitempty"`
}
func (g Game) Finished() bool {
return len(g.Winner) > 0
}
type GameMeta struct {
Battles []BattleMeta `json:"battles,omitempty"`
Bombings []Bombing `json:"bombings,omitempty"`
}
type BattleMeta struct {
Turn uint `json:"turn"`
Planet uint `json:"planet"`
BattleID uuid.UUID `json:"battle_id"`
ObserverIDs []uuid.UUID `json:"observer_ids"`
}
func (g Game) MarshalBinary() (data []byte, err error) {
return json.Marshal(&g)
}
func (g *Game) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, g)
}
func (gm GameMeta) MarshalBinary() (data []byte, err error) {
return json.Marshal(&gm)
}
func (gm *GameMeta) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, gm)
}
+54
View File
@@ -0,0 +1,54 @@
package game_test
import (
"testing"
"github.com/iliadenisov/galaxy/server/internal/model/game"
"github.com/stretchr/testify/assert"
)
func TestTechSet(t *testing.T) {
s := ts.Set(game.TechDrive, 10.5)
assert.Equal(t, 1.1, ts.Value(game.TechDrive))
assert.Equal(t, 1.2, ts.Value(game.TechWeapons))
assert.Equal(t, 1.3, ts.Value(game.TechShields))
assert.Equal(t, 1.4, ts.Value(game.TechCargo))
assert.Equal(t, 10.5, s.Value(game.TechDrive))
assert.Equal(t, 1.2, s.Value(game.TechWeapons))
assert.Equal(t, 1.3, s.Value(game.TechShields))
assert.Equal(t, 1.4, s.Value(game.TechCargo))
s = s.Set(game.TechWeapons, 5.7)
assert.Equal(t, 1.1, ts.Value(game.TechDrive))
assert.Equal(t, 1.2, ts.Value(game.TechWeapons))
assert.Equal(t, 1.3, ts.Value(game.TechShields))
assert.Equal(t, 1.4, ts.Value(game.TechCargo))
assert.Equal(t, 10.5, s.Value(game.TechDrive))
assert.Equal(t, 5.7, s.Value(game.TechWeapons))
assert.Equal(t, 1.3, s.Value(game.TechShields))
assert.Equal(t, 1.4, s.Value(game.TechCargo))
s = s.Set(game.TechShields, 2.13)
assert.Equal(t, 1.1, ts.Value(game.TechDrive))
assert.Equal(t, 1.2, ts.Value(game.TechWeapons))
assert.Equal(t, 1.3, ts.Value(game.TechShields))
assert.Equal(t, 1.4, ts.Value(game.TechCargo))
assert.Equal(t, 10.5, s.Value(game.TechDrive))
assert.Equal(t, 5.7, s.Value(game.TechWeapons))
assert.Equal(t, 2.13, s.Value(game.TechShields))
assert.Equal(t, 1.4, s.Value(game.TechCargo))
s = s.Set(game.TechCargo, 3.1415926)
assert.Equal(t, 1.1, ts.Value(game.TechDrive))
assert.Equal(t, 1.2, ts.Value(game.TechWeapons))
assert.Equal(t, 1.3, ts.Value(game.TechShields))
assert.Equal(t, 1.4, ts.Value(game.TechCargo))
assert.Equal(t, 10.5, s.Value(game.TechDrive))
assert.Equal(t, 5.7, s.Value(game.TechWeapons))
assert.Equal(t, 2.13, s.Value(game.TechShields))
assert.Equal(t, 3.1415926, s.Value(game.TechCargo))
}
+233
View File
@@ -0,0 +1,233 @@
package game
import (
"fmt"
"math"
"strings"
"github.com/google/uuid"
)
type CargoType string
const (
CargoColonist CargoType = "COL" // Колонисты
CargoMaterial CargoType = "MAT" // Сырьё
CargoCapital CargoType = "CAP" // Промышленность
)
var (
CargoTypeSet map[string]CargoType = map[string]CargoType{
strings.ToLower(CargoColonist.String()): CargoColonist,
strings.ToLower(CargoMaterial.String()): CargoMaterial,
strings.ToLower(CargoCapital.String()): CargoCapital,
}
)
func (ct CargoType) Ref() *CargoType {
return &ct
}
func (ct CargoType) String() string {
return string(ct)
}
type ShipGroupState string
const (
StateInOrbit ShipGroupState = "In_Orbit"
StateLaunched ShipGroupState = "Launched"
StateInSpace ShipGroupState = "In_Space"
StateUpgrade ShipGroupState = "Upgrade"
StateTransfer ShipGroupState = "Transfer" // [ ] Группы будут передаваться мгновенно в начале производства хода
)
func (sgs ShipGroupState) String() string {
return string(sgs)
}
type InSpace struct {
// X, Y are nil for Launched state
Origin uint `json:"origin"`
X *Float `json:"x,omitempty"`
Y *Float `json:"y,omitempty"`
}
func (is InSpace) Equal(other InSpace) bool {
return is.Origin == other.Origin && is.X == other.X && is.Y == other.Y
}
func (is InSpace) Launched() bool {
if (is.X == nil && is.Y != nil) || (is.X != nil && is.Y == nil) {
panic("group in space state invalid: one of coordinate is not set")
}
return is.X == nil && is.Y == is.X
}
type InUpgrade struct {
UpgradeTech []UpgradePreference `json:"preference"`
}
func (iu InUpgrade) Cost() float64 {
var sum float64
for i := range iu.UpgradeTech {
sum += iu.UpgradeTech[i].Cost.F()
}
return sum
}
func (iu InUpgrade) TechCost(t Tech) float64 {
for i := range iu.UpgradeTech {
if iu.UpgradeTech[i].Tech == t {
return iu.UpgradeTech[i].Cost.F()
}
}
return 0.
}
type UpgradePreference struct {
Tech Tech `json:"tech"`
Level Float `json:"level"`
Cost Float `json:"cost"`
}
type Tech string
const (
TechAll Tech = "ALL"
TechDrive Tech = "DRIVE"
TechWeapons Tech = "WEAPONS"
TechShields Tech = "SHIELDS"
TechCargo Tech = "CARGO"
)
func (t Tech) String() string {
return string(t)
}
type ShipGroup struct {
ID uuid.UUID `json:"id"`
OwnerID uuid.UUID `json:"ownerId"` // Race reference
TypeID uuid.UUID `json:"typeId"` // ShipType reference
FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet reference
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"` //
StateInSpace *InSpace `json:"inSpace,omitempty"` //
StateUpgrade *InUpgrade `json:"upgrade,omitempty"` //
StateTransfer bool `json:"transfer,omitempty"` //
}
func (sg ShipGroup) TechLevel(t Tech) Float {
return F(sg.Tech.Value(t))
}
func (sg *ShipGroup) SetTechLevel(t Tech, v float64) {
sg.Tech = sg.Tech.Set(t, v)
}
func (sg ShipGroup) State() ShipGroupState {
switch {
case sg.StateInSpace == nil && sg.StateUpgrade == nil:
return StateInOrbit
case sg.StateInSpace != nil && sg.StateUpgrade == nil:
if !sg.StateInSpace.Launched() {
return StateInSpace
}
if sg.StateTransfer {
return StateTransfer
}
return StateLaunched
case sg.StateUpgrade != nil && sg.StateInSpace == nil:
return StateUpgrade
default:
panic(fmt.Sprintf("ambigous group state: in_space=%#v upgrage=%#v", sg.StateInSpace, sg.StateUpgrade))
}
}
func (sg ShipGroup) AtPlanet() (uint, bool) {
switch sg.State() {
case StateInOrbit:
return sg.Destination, true
case StateLaunched:
return sg.StateInSpace.Origin, true
default:
return 0, false
}
}
func (sg ShipGroup) Coord() (float64, float64, bool) {
state := sg.State()
if state == StateInSpace || state == StateLaunched {
return sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), true
}
return 0, 0, false
}
func (sg ShipGroup) Equal(other ShipGroup) bool {
return sg.OwnerID == other.OwnerID &&
sg.TypeID == other.TypeID &&
sg.FleetID == other.FleetID &&
sg.TechLevel(TechDrive) == other.TechLevel(TechDrive) &&
sg.TechLevel(TechWeapons) == other.TechLevel(TechWeapons) &&
sg.TechLevel(TechShields) == other.TechLevel(TechShields) &&
sg.TechLevel(TechCargo) == other.TechLevel(TechCargo) &&
sg.CargoType == other.CargoType &&
(sg.Load/F(float64(sg.Number))).F() == (other.Load/F(float64(other.Number))).F() &&
sg.State() == other.State()
}
// Грузоподъёмность
func (sg ShipGroup) CargoCapacity(st *ShipType) float64 {
return sg.TechLevel(TechCargo).F() * (st.Cargo.F() + (st.Cargo.F()*st.Cargo.F())/20) * float64(sg.Number)
}
// Масса перевозимого груза -
// общее количество единиц груза, деленное на технологический уровень Грузоперевозок
func (sg ShipGroup) CarryingMass() float64 {
if sg.Load.F() == 0 {
return 0
}
return sg.Load.F() / sg.TechLevel(TechCargo).F()
}
// Масса группы без учёта груза
func (sg ShipGroup) EmptyMass(st *ShipType) float64 {
return st.EmptyMass() * float64(sg.Number)
}
// Полная масса -
// массу корабля самого по себе плюс масса перевозимого груза
func (sg ShipGroup) FullMass(st *ShipType) float64 {
return sg.EmptyMass(st) + sg.CarryingMass()
}
// Эффективность двигателя -
// равна мощности Двигателей, умноженной на технологический уровень блока Двигателей
func (sg ShipGroup) DriveEffective(st *ShipType) float64 {
return st.Drive.F() * sg.TechLevel(TechDrive).F()
}
// Корабли перемещаются за один ход на количество световых лет, равное
// эффективности двигателя, умноженной на 20 и деленной на "Полную массу" корабля
func (sg ShipGroup) Speed(st *ShipType) float64 {
return sg.DriveEffective(st) * 20 / sg.FullMass(st)
}
// Мощность бомбардировки
func (sg ShipGroup) BombingPower(st *ShipType) float64 {
return (math.Sqrt(st.Weapons.F()*sg.TechLevel(TechWeapons).F())/10. + 1.) *
st.Weapons.F() *
sg.TechLevel(TechWeapons).F() *
float64(st.Armament) *
float64(sg.Number)
}
func (sg ShipGroup) CargoString() string {
if sg.CargoType == nil {
return "-"
}
return sg.CargoType.String()
}
+252
View File
@@ -0,0 +1,252 @@
package game_test
import (
"math/rand/v2"
"testing"
"galaxy/util"
"github.com/google/uuid"
"github.com/iliadenisov/galaxy/server/internal/model/game"
"github.com/stretchr/testify/assert"
)
func TestCargoCapacity(t *testing.T) {
test := func(cargoSize float64, expectCapacity float64) {
ship := game.ShipType{
Drive: 1,
Armament: 1,
Weapons: 1,
Shields: 1,
Cargo: game.F(cargoSize),
}
sg := game.ShipGroup{
Number: 1,
Tech: map[game.Tech]game.Float{
game.TechDrive: game.F(1.5),
game.TechWeapons: game.F(1.1),
game.TechShields: game.F(2.0),
game.TechCargo: game.F(1.0),
},
}
assert.Equal(t, expectCapacity, sg.CargoCapacity(&ship))
}
test(1, 1.05)
test(5, 6.25)
test(10, 15)
test(50, 175)
test(100, 600)
}
func TestCarryingAndFullMass(t *testing.T) {
Freighter := &game.ShipType{
Name: "Freighter",
Drive: 8,
Armament: 0,
Weapons: 0,
Shields: 2,
Cargo: 10,
}
sg := &game.ShipGroup{
Number: 1,
Tech: map[game.Tech]game.Float{
game.TechDrive: game.F(1.0),
game.TechWeapons: game.F(1.0),
game.TechShields: game.F(1.0),
game.TechCargo: game.F(1.0),
},
Load: 0.0,
}
em := Freighter.EmptyMass()
assert.Equal(t, 0.0, sg.CarryingMass())
assert.Equal(t, em, sg.FullMass(Freighter))
sg.Load = 10.0
assert.Equal(t, 10.0, sg.CarryingMass())
assert.Equal(t, em+10.0, sg.FullMass(Freighter))
sg.SetTechLevel(game.TechCargo, 2.5)
assert.Equal(t, 4.0, sg.CarryingMass())
assert.Equal(t, em+4.0, sg.FullMass(Freighter))
}
func TestSpeed(t *testing.T) {
Freighter := &game.ShipType{
Name: "Freighter",
Drive: 8,
Armament: 0,
Weapons: 0,
Shields: 2,
Cargo: 10,
}
sg := &game.ShipGroup{
Number: 1,
Tech: map[game.Tech]game.Float{
game.TechDrive: game.F(1.0),
game.TechWeapons: game.F(1.0),
game.TechShields: game.F(1.0),
game.TechCargo: game.F(1.0),
},
Load: 0.0,
}
assert.Equal(t, 8.0, sg.Speed(Freighter))
sg.Load = 5.0
assert.Equal(t, 6.4, sg.Speed(Freighter))
sg.SetTechLevel(game.TechDrive, 1.5)
assert.Equal(t, 9.6, sg.Speed(Freighter))
sg.Load = 10
sg.SetTechLevel(game.TechCargo, 1.5)
assert.Equal(t, 9.0, sg.Speed(Freighter))
}
func TestBombingPower(t *testing.T) {
BattleStation := game.ShipType{
Name: "Battle_Station",
Drive: 60.0,
Armament: 3,
Weapons: 30.0,
Shields: 100.0,
Cargo: 0.0,
}
sg := game.ShipGroup{
Number: 1,
Tech: map[game.Tech]game.Float{
game.TechDrive: game.F(1.0),
game.TechWeapons: game.F(1.0),
game.TechShields: game.F(1.0),
game.TechCargo: game.F(1.0),
},
}
assert.Equal(t, 139.295, util.Fixed3(sg.BombingPower(&BattleStation)))
sg.Number = 2
assert.Equal(t, 278.590, util.Fixed3(sg.BombingPower(&BattleStation)))
}
func TestDriveEffective(t *testing.T) {
tc := []struct {
driveShipType game.Float
driveTech game.Float
expectDriveEffective game.Float
}{
{1, 1, 1},
{1, 2, 2},
{2, 1, 2},
{0, 1, 0},
{0, 1.5, 0},
{0, 10, 0},
{1.5, 1.5, 2.25},
}
for i := range tc {
someShip := game.ShipType{
Drive: tc[i].driveShipType,
Armament: rand.UintN(30) + 1,
Weapons: game.F(rand.Float64()*30 + 1),
Shields: game.F(rand.Float64()*100 + 1),
Cargo: game.F(rand.Float64()*20 + 1),
}
sg := game.ShipGroup{
Number: rand.UintN(4) + 1,
Tech: map[game.Tech]game.Float{
game.TechDrive: tc[i].driveTech,
game.TechWeapons: game.F(rand.Float64()*5 + 1),
game.TechShields: game.F(rand.Float64()*5 + 1),
game.TechCargo: game.F(rand.Float64()*5 + 1),
},
}
assert.Equal(t, tc[i].expectDriveEffective.F(), sg.DriveEffective(&someShip))
}
}
func TestShipGroupEqual(t *testing.T) {
fleetId := uuid.New()
someUUID := uuid.New()
mat := game.CargoMaterial
cap := game.CargoCapital
left := &game.ShipGroup{
ID: uuid.New(),
Number: 1,
OwnerID: uuid.New(),
TypeID: uuid.New(),
FleetID: &fleetId,
CargoType: &mat,
Load: 123.45,
Tech: map[game.Tech]game.Float{
game.TechDrive: 1.0,
game.TechWeapons: 1.0,
game.TechShields: 1.0,
game.TechCargo: 1.0,
},
}
// essential properties
right := *left
assert.True(t, left.Equal(right))
left.OwnerID = someUUID
assert.False(t, left.Equal(right))
right = *left
left.TypeID = someUUID
assert.False(t, left.Equal(right))
right = *left
left.FleetID = &someUUID
assert.False(t, left.Equal(right))
right = *left
left.FleetID = nil
assert.False(t, left.Equal(right))
right = *left
coord := game.Float(1)
left.StateInSpace = &game.InSpace{
Origin: 1,
X: &coord,
Y: &coord,
}
assert.False(t, left.Equal(right))
right = *left
left.CargoType = &cap
assert.False(t, left.Equal(right))
right = *left
left.CargoType = nil
assert.False(t, left.Equal(right))
right = *left
left.Load = 45.123
assert.False(t, left.Equal(right))
right = *left
left.SetTechLevel(game.TechDrive, 1.1)
assert.Equal(t, 1.1, left.TechLevel(game.TechDrive).F())
assert.False(t, left.Equal(right))
right = *left
left.SetTechLevel(game.TechWeapons, 1.1)
assert.Equal(t, 1.1, left.TechLevel(game.TechWeapons).F())
assert.False(t, left.Equal(right))
right = *left
left.SetTechLevel(game.TechShields, 1.1)
assert.Equal(t, 1.1, left.TechLevel(game.TechShields).F())
assert.False(t, left.Equal(right))
right = *left
left.SetTechLevel(game.TechCargo, 1.1)
assert.Equal(t, 1.1, left.TechLevel(game.TechCargo).F())
assert.False(t, left.Equal(right))
// non-essential properties
right = *left
left.ID = uuid.New()
assert.True(t, left.Equal(right))
// dirty hack to equalize loads
left.Number = 5
left.Load = game.F(float64(right.Load) / float64(right.Number) * float64(left.Number))
assert.True(t, left.Equal(right))
}
+7
View File
@@ -0,0 +1,7 @@
package game
type Map struct {
Width uint32 `json:"width"`
Height uint32 `json:"height"`
Planet []Planet `json:"planets"`
}
+167
View File
@@ -0,0 +1,167 @@
package game
import (
"github.com/google/uuid"
)
type Planet struct {
X Float `json:"x"`
Y Float `json:"y"`
Number uint `json:"number"`
Size Float `json:"size"`
Name string `json:"name"`
Owner *uuid.UUID `json:"owner,omitempty"`
Resources Float `json:"resources"` // R - Ресурсы
Capital Float `json:"capital"` // CAP $ - Запасы промышленности
Material Float `json:"material"` // MAT M - Запасы ресурсов / сырьё
Industry Float `json:"industry"` // I - Промышленность
Population Float `json:"population"` // P - Население
Colonists Float `json:"colonists"` // COL C - Количество колонистов
Production Production `json:"production"`
Route map[RouteType]uint `json:"route"`
}
func (p *Planet) Own(v uuid.UUID) {
if v == uuid.Nil {
p.Free()
return
}
if p.Owner == nil || *p.Owner != v {
p.Production = ProductionCapital.AsType(uuid.Nil)
}
p.Owner = &v
}
func (p *Planet) Free() {
p.Owner = nil
p.Production = ProductionNone.AsType(uuid.Nil)
p.Colonists = 0.
p.Population = 0.
clear(p.Route)
}
func (p *Planet) Wipe() {
p.Free()
p.Industry = 0
p.Capital = 0
}
func (p Planet) Owned() bool {
return p.Owner != nil && *p.Owner != uuid.Nil
}
func (p Planet) OwnedBy(v uuid.UUID) bool {
if !p.Owned() {
return false
}
return *p.Owner == v
}
func (p *Planet) Mat(v float64) {
p.Material = F(v)
}
func (p *Planet) Pop(v float64) {
p.Population = F(v)
}
func (p *Planet) Col(v float64) {
p.Colonists = F(v)
}
func (p *Planet) Ind(v float64) {
p.Industry = F(v)
}
func (p *Planet) Cap(v float64) {
p.Capital = F(v)
}
func (p Planet) Votes() float64 {
return p.Population.F() / 1000.
}
// Производственный потенциал без учёта модернизации кораблей
func (p Planet) ProductionCapacity() float64 {
return PlanetProduction(p.Industry.F(), p.Population.F())
}
func PlanetProduction(industry, population float64) float64 {
return industry*0.75 + population*0.25
}
func (p *Planet) ReleaseMaterial(shipMass float64) {
if p.Production.Type != ProductionShip || p.Production.Progress == nil {
panic("planet is not producing any ships")
}
p.Material = p.Material.Add(ProducedMaterial(shipMass, float64(*p.Production.Progress)))
p.Production.Progress = new(Float)
p.Production.ProdUsed = new(Float)
}
func ProducedMaterial(shipMass, progress float64) float64 {
return shipMass * progress
}
// Производство промышленности
func (p *Planet) ProduceIndustry(freeProduction float64) {
prod := freeProduction / 5
if float64(p.Material) < prod {
prod = (freeProduction + float64(p.Material/p.Resources)) / (5. + 1./float64(p.Resources))
p.Material = 0.
} else {
p.Material = p.Material.Add(-prod)
}
p.Industry = p.Industry.Add(prod)
if p.Industry > p.Population {
p.Capital += p.Industry - p.Population
p.Industry = p.Population
}
}
// Производство материалов
func (p *Planet) ProduceMaterial(freeProduction float64) {
p.Material = p.Material.Add(freeProduction * p.Resources.F())
}
// Автоматическое увеличение населения на каждом ходу
func (p *Planet) ProducePopulation() {
p.Population *= 1.08
if p.Population > p.Size {
p.Colonists += (p.Population - p.Size) / 8.
p.Population = p.Size
}
}
func (p *Planet) UnpackCapital() {
if p.Capital == 0 {
return
}
deficit := p.Population - p.Industry
if deficit > p.Capital {
deficit = p.Capital
}
p.Capital -= deficit
p.Industry += deficit
}
func (p *Planet) UnpackColonists() {
if p.Colonists == 0 {
return
}
deficit := (p.Size - p.Population) / 8
if deficit > p.Colonists {
deficit = p.Colonists
}
p.Colonists -= deficit
p.Population += deficit * 8
}
func UnloadColonists(p Planet, v float64) Planet {
p.Pop(p.Population.F() + v*8)
if p.Population > p.Size {
p.Col(p.Colonists.F() + (p.Population.F()-p.Size.F())/8.)
p.Pop(p.Size.F())
}
return p
}
+151
View File
@@ -0,0 +1,151 @@
package game_test
import (
"testing"
"github.com/iliadenisov/galaxy/server/internal/model/game"
"github.com/stretchr/testify/assert"
)
func TestPlanetProduction(t *testing.T) {
assert.Equal(t, 1000., game.PlanetProduction(1000., 1000.))
assert.Equal(t, 625., game.PlanetProduction(500., 1000.))
assert.Equal(t, 750., game.PlanetProduction(1000., 0.))
assert.Equal(t, 250., game.PlanetProduction(0., 1000.))
}
func TestProduceIndustry(t *testing.T) {
HW := &game.Planet{
Size: 1000,
Resources: 10,
Population: 1000,
Industry: 1000,
}
DW := &game.Planet{
Size: 500,
Resources: 10,
Population: 500,
Industry: 500,
}
HW.ProduceIndustry(HW.ProductionCapacity())
assert.InDelta(t, 196.078, HW.Capital.F(), 0.0005)
HW.Capital = 0
HW.Material = 200
HW.ProduceIndustry(HW.ProductionCapacity())
assert.Equal(t, 200., HW.Capital.F())
assert.Equal(t, 0., HW.Material.F())
DW.ProduceIndustry(DW.ProductionCapacity())
assert.InDelta(t, 98.039, DW.Capital.F(), 0.0003)
DW.Capital = 0
DW.Material = 100
DW.ProduceIndustry(DW.ProductionCapacity())
assert.Equal(t, 100., DW.Capital.F())
assert.Equal(t, 0., DW.Material.F())
}
func TestProduceMaterial(t *testing.T) {
HW := &game.Planet{
Size: 1000,
Resources: 10,
Population: 1000,
Industry: 1000,
}
assert.Equal(t, 0., HW.Material.F())
HW.ProduceMaterial(HW.ProductionCapacity())
assert.Equal(t, 10000., HW.Material.F())
HW.Industry = 500
HW.Population = 500
HW.ProduceMaterial(HW.ProductionCapacity())
assert.Equal(t, 15000., HW.Material.F())
HW.Population = 1000
HW.ProduceMaterial(HW.ProductionCapacity())
assert.Equal(t, 21250., HW.Material.F())
}
func TestUnpackCapital(t *testing.T) {
HW := &game.Planet{
Size: 1000,
Resources: 10,
Population: 1000,
Industry: 1000,
}
assert.Equal(t, 0., HW.Capital.F())
HW.UnpackCapital()
assert.Equal(t, 1000., HW.Industry.F())
assert.Equal(t, 0., HW.Capital.F())
HW.Capital = 123.
HW.UnpackCapital()
assert.Equal(t, 1000., HW.Industry.F())
assert.Equal(t, 123., HW.Capital.F())
HW.Industry = 987.
HW.UnpackCapital()
assert.Equal(t, 1000., HW.Industry.F())
assert.Equal(t, 110., HW.Capital.F())
HW.Population = 876.
HW.Industry = 800.
HW.UnpackCapital()
assert.Equal(t, 876., HW.Population.F())
assert.Equal(t, 876., HW.Industry.F())
assert.Equal(t, 34., HW.Capital.F())
}
func TestUnpackColonists(t *testing.T) {
HW := &game.Planet{
Size: 1000,
Resources: 10,
Population: 1000,
Industry: 1000,
}
assert.Equal(t, 0., HW.Colonists.F())
HW.UnpackColonists()
assert.Equal(t, 1000., HW.Population.F())
assert.Equal(t, 0., HW.Colonists.F())
HW.Colonists = 1.05
HW.UnpackColonists()
assert.Equal(t, 1000., HW.Population.F())
assert.Equal(t, 1.05, HW.Colonists.F())
HW.Population = 996.0
HW.UnpackColonists()
assert.Equal(t, 1000., HW.Population.F())
assert.Equal(t, 0.55, HW.Colonists.F())
HW.Population = 0.0
HW.UnpackColonists()
assert.Equal(t, 4.4, HW.Population.F())
assert.Equal(t, 0., HW.Colonists.F())
}
func TestProducePopulation(t *testing.T) {
HW := &game.Planet{
Size: 1000,
Resources: 10,
Population: 500,
Industry: 1000,
}
assert.Equal(t, 500., HW.Population.F())
assert.Equal(t, 0., HW.Colonists.F())
HW.ProducePopulation()
assert.Equal(t, 540., HW.Population.F())
assert.Equal(t, 0., HW.Colonists.F())
HW.Population = 1000.
HW.ProducePopulation()
assert.Equal(t, 1000., HW.Population.F())
assert.Equal(t, 10., HW.Colonists.F())
}
+37
View File
@@ -0,0 +1,37 @@
package game
import (
"github.com/google/uuid"
)
type ProductionType string
const (
ProductionNone ProductionType = "-"
ProductionMaterial ProductionType = "MAT" // Сырьё
ProductionCapital ProductionType = "CAP" // Промышленность
ResearchDrive ProductionType = "DRIVE"
ResearchWeapons ProductionType = "WEAPONS"
ResearchShields ProductionType = "SHIELDS"
ResearchCargo ProductionType = "CARGO"
ResearchScience ProductionType = "SCIENCE"
ProductionShip ProductionType = "SHIP"
)
type Production struct {
Type ProductionType `json:"type"`
SubjectID *uuid.UUID `json:"subjectId,omitempty"`
Progress *Float `json:"progress,omitempty"`
ProdUsed *Float `json:"prodUsed,omitempty"`
}
func (p ProductionType) AsType(subject uuid.UUID) Production {
switch p {
case ResearchScience, ProductionShip:
return Production{Type: p, SubjectID: &subject}
default:
return Production{Type: p, SubjectID: nil}
}
}
+62
View File
@@ -0,0 +1,62 @@
package game
import (
"strings"
"github.com/google/uuid"
)
type Relation string
const (
RelationWar Relation = "WAR"
RelationPeace Relation = "PEACE"
)
var (
relationSet = map[string]Relation{
strings.ToLower(RelationWar.String()): RelationWar,
strings.ToLower(RelationPeace.String()): RelationPeace,
}
)
type Race struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
TTL uint `json:"ttl"`
Extinct bool `json:"extinct"`
Votes Float `json:"votes"`
VoteFor uuid.UUID `json:"voteFor"`
Relations []RaceRelation `json:"relations"`
Tech TechSet `json:"tech"`
Sciences []Science `json:"science,omitempty"`
ShipTypes []ShipType `json:"shipType,omitempty"`
}
func ParseRelation(v string) (Relation, bool) {
if v, ok := relationSet[strings.ToLower(v)]; ok {
return v, ok
}
return Relation(""), false
}
func (r Relation) String() string {
return string(r)
}
type RaceRelation struct {
RaceID uuid.UUID `json:"raceId"`
Relation Relation `json:"relation"`
}
func (r Race) TechLevel(t Tech) float64 {
return r.Tech.Value(t)
}
func (r Race) FlightDistance() float64 {
return r.TechLevel(TechDrive) * 40
}
func (r Race) VisibilityDistance() float64 {
return r.TechLevel(TechDrive) * 30
}
+35
View File
@@ -0,0 +1,35 @@
package game_test
import (
"testing"
"github.com/iliadenisov/galaxy/server/internal/model/game"
"github.com/stretchr/testify/assert"
)
var (
ts = game.TechSet{
game.TechDrive: 1.1,
game.TechWeapons: 1.2,
game.TechShields: 1.3,
game.TechCargo: 1.4,
}
r = game.Race{
Tech: ts,
}
)
func TestTechLevel(t *testing.T) {
assert.Equal(t, 1.1, r.TechLevel(game.TechDrive))
assert.Equal(t, 1.2, r.TechLevel(game.TechWeapons))
assert.Equal(t, 1.3, r.TechLevel(game.TechShields))
assert.Equal(t, 1.4, r.TechLevel(game.TechCargo))
}
func TestFlightDistance(t *testing.T) {
assert.Equal(t, 44., r.FlightDistance())
}
func TestVisibilityDistance(t *testing.T) {
assert.Equal(t, 33., r.VisibilityDistance())
}
+34
View File
@@ -0,0 +1,34 @@
package game
import "strings"
type RouteType string
const (
RouteMaterial RouteType = "MAT" // Сырьё
RouteCapital RouteType = "CAP" // Промышленность
RouteColonist RouteType = "COL" // Колонисты
RouteEmpty RouteType = "EMP" // Пустые корабли
)
var (
RouteTypeSet map[string]RouteType = map[string]RouteType{
strings.ToLower(RouteMaterial.String()): RouteMaterial,
strings.ToLower(RouteCapital.String()): RouteCapital,
strings.ToLower(RouteColonist.String()): RouteColonist,
strings.ToLower(RouteEmpty.String()): RouteEmpty,
}
RouteToCargo map[RouteType]CargoType = map[RouteType]CargoType{
RouteColonist: CargoColonist,
RouteCapital: CargoCapital,
RouteMaterial: CargoMaterial,
}
)
func (rt RouteType) Ref() *RouteType {
return &rt
}
func (rt RouteType) String() string {
return string(rt)
}
+14
View File
@@ -0,0 +1,14 @@
package game
import (
"github.com/google/uuid"
)
type Science struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Drive Float `json:"drive"`
Weapons Float `json:"weapons"`
Shields Float `json:"shields"`
Cargo Float `json:"cargo"`
}
+64
View File
@@ -0,0 +1,64 @@
package game
import (
"fmt"
"github.com/google/uuid"
)
type ShipType struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Drive Float `json:"drive"`
Armament uint `json:"armament"`
Weapons Float `json:"weapons"`
Shields Float `json:"shields"`
Cargo Float `json:"cargo"`
}
func (st ShipType) Equal(o ShipType) bool {
return st.Drive == o.Drive &&
st.Weapons == o.Weapons &&
st.Armament == o.Armament &&
st.Shields == o.Shields &&
st.Cargo == o.Cargo
}
func (st ShipType) BlockMass(t Tech) float64 {
switch t {
case TechDrive:
return st.DriveBlockMass()
case TechWeapons:
return st.WeaponsBlockMass()
case TechShields:
return st.ShieldsBlockMass()
case TechCargo:
return st.CargoBlockMass()
default:
panic("BlockMass: unexpectec tech: " + t.String())
}
}
func (st ShipType) DriveBlockMass() float64 {
return st.Drive.F()
}
func (st ShipType) WeaponsBlockMass() float64 {
if (st.Armament == 0 && st.Weapons != 0) || (st.Armament != 0 && st.Weapons == 0) {
panic(fmt.Sprintf("ship class invalid design: A=%d W=%.03f", st.Armament, st.Weapons))
}
return float64(st.Armament+1) * (st.Weapons.F() / 2)
}
func (st ShipType) ShieldsBlockMass() float64 {
return st.Shields.F()
}
func (st ShipType) CargoBlockMass() float64 {
return st.Cargo.F()
}
func (st ShipType) EmptyMass() float64 {
shipMass := st.DriveBlockMass() + st.ShieldsBlockMass() + st.CargoBlockMass() + st.WeaponsBlockMass()
return shipMass
}
+40
View File
@@ -0,0 +1,40 @@
package game_test
import (
"testing"
"github.com/iliadenisov/galaxy/server/internal/model/game"
"github.com/stretchr/testify/assert"
)
func TestEmptyMass(t *testing.T) {
Freighter := game.ShipType{
Name: "Freighter",
Drive: 8,
Armament: 0,
Weapons: 0,
Shields: 2,
Cargo: 10,
}
assert.Equal(t, 20., Freighter.EmptyMass())
Gunship := game.ShipType{
Name: "Gunship",
Drive: 4,
Armament: 2,
Weapons: 2,
Shields: 4,
Cargo: 0,
}
assert.Equal(t, 11., Gunship.EmptyMass())
Cruiser := game.ShipType{
Name: "Cruiser",
Drive: 15,
Armament: 1,
Weapons: 15,
Shields: 15,
Cargo: 0,
}
assert.Equal(t, 45., Cruiser.EmptyMass())
}
+16
View File
@@ -0,0 +1,16 @@
package game
import "github.com/google/uuid"
type State struct {
ID uuid.UUID
Turn uint
Stage uint
Players []PlayerState
}
type PlayerState struct {
ID uuid.UUID
Name string
Extinct bool
}