refactor: game funcs moved to controller

This commit is contained in:
Ilia Denisov
2026-01-15 14:42:04 +02:00
parent fe8a8d4150
commit 16aba8435d
47 changed files with 1023 additions and 3093 deletions
-239
View File
@@ -2,247 +2,8 @@ package game
import "github.com/google/uuid"
// import (
// "fmt"
// "iter"
// "math"
// "slices"
// "github.com/google/uuid"
// e "github.com/iliadenisov/galaxy/internal/error"
// )
type Fleet struct {
ID uuid.UUID `json:"id"`
OwnerID uuid.UUID `json:"ownerId"`
Name string `json:"name"`
}
// func FleetState(g *Game, fleetID uuid.UUID) (ShipGroupState, *uint, *InSpace) {
// fi := slices.IndexFunc(g.Fleets, func(f Fleet) bool { return f.ID == fleetID })
// if fi < 0 {
// panic("FleetState: fleet id not found: " + fleetID.String())
// }
// ri := slices.IndexFunc(g.Race, func(r Race) bool { return r.ID == g.Fleets[fi].OwnerID })
// if ri < 0 {
// panic("FleetState: race id not found: " + g.Fleets[fi].OwnerID.String())
// }
// var state *ShipGroupState
// var onPlanet *uint
// var is *InSpace
// for sg := range FleetGroups(g, ri, fi) {
// if state == nil {
// s := sg.State()
// state = &s
// if planet, ok := sg.OnPlanet(); ok {
// onPlanet = &planet
// }
// is = sg.StateInSpace
// continue
// }
// if *state != sg.State() {
// panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different states", g.Race[ri].Name, g.Fleets[fi].Name))
// }
// if planet, ok := sg.OnPlanet(); ok && onPlanet != nil && *onPlanet != planet {
// for sg := range FleetGroups(g, ri, fi) {
// fmt.Println("group", sg.Index, "fleet", sg.FleetID, g.Fleets[fi].Name, "state", sg.State(), "on", sg.Destination)
// }
// panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q are on different planets: %d <> %d", g.Race[ri].Name, g.Fleets[fi].Name, *onPlanet, planet))
// }
// if (is == nil && sg.StateInSpace != nil) || (is != nil && sg.StateInSpace == nil) {
// panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q on_planet and in_space at the same time", g.Race[ri].Name, g.Fleets[fi].Name))
// }
// if is != nil && sg.StateInSpace != nil && !is.Equal(*sg.StateInSpace) {
// panic(fmt.Sprintf("FleetState: one or more ships in race's %q fleet %q has different is_space states", g.Race[ri].Name, g.Fleets[fi].Name))
// }
// }
// if state == nil {
// panic(fmt.Sprintf("FleetState: race's %q fleet %q has no ships", g.Race[ri].Name, g.Fleets[fi].Name))
// }
// return *state, onPlanet, is
// }
// // TODO: Hello! Wanna know fleet's speed? Good. Implement & test this func first.
// func (g Game) FleetSpeed(fl Fleet) float64 {
// result := math.MaxFloat64
// for sg := range g.ShipGroups {
// if g.ShipGroups[sg].FleetID == nil || *g.ShipGroups[sg].FleetID != fl.ID {
// continue
// }
// st := g.mustShipType(g.ShipGroups[sg].TypeID)
// typeSpeed := g.ShipGroups[sg].Speed(st)
// if typeSpeed < result {
// result = typeSpeed
// }
// }
// return result
// }
// func (g *Game) JoinShipGroupToFleet(raceName, fleetName string, group, count uint) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// return g.joinShipGroupToFleetInternal(ri, fleetName, group, count)
// }
// func (g *Game) JoinFleets(raceName, fleetSourceName, fleetTargetName string) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// return g.joinFleetsInternal(ri, fleetSourceName, fleetTargetName)
// }
// func (g *Game) joinShipGroupToFleetInternal(ri int, fleetName string, groupIndex, quantity uint) (err error) {
// name, ok := validateTypeName(fleetName)
// if !ok {
// return e.NewEntityTypeNameValidationError("%q", name)
// }
// sgi := -1
// var maxIndex uint
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// if sg.Index > maxIndex {
// maxIndex = sg.Index
// }
// }
// if sgi < 0 {
// return e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// if g.ShipGroups[sgi].State() != StateInOrbit {
// return e.NewShipsBusyError()
// }
// if g.ShipGroups[sgi].Number < quantity {
// return e.NewJoinFleetGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
// }
// fi := g.fleetIndex(ri, name)
// if fi < 0 {
// fi, err = g.createFleet(ri, name)
// if err != nil {
// return err
// }
// } else {
// state, onPlanet, _ := FleetState(g, g.Fleets[fi].ID)
// if state != StateInOrbit || *onPlanet != g.ShipGroups[sgi].Destination {
// return e.NewShipsNotOnSamePlanetError("fleet: %s", fleetName)
// }
// }
// // FIXME: if g.ShipGroups[sgi].FleetID != nil { // delete old fleet if empty, ALSO mind breaking group }
// if quantity > 0 && quantity < g.ShipGroups[sgi].Number {
// // nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity)
// // if err != nil {
// // return err
// // }
// // sgi = nsgi
// newGroup := g.ShipGroups[sgi]
// newGroup.Number -= quantity
// g.ShipGroups[sgi].Number = quantity
// newGroup.Index = maxIndex + 1
// g.ShipGroups = append(g.ShipGroups, newGroup)
// }
// g.ShipGroups[sgi].FleetID = &g.Fleets[fi].ID
// return nil
// }
// func (g *Game) createFleet(ri int, name string) (int, error) {
// n, ok := validateTypeName(name)
// if !ok {
// return 0, e.NewEntityTypeNameValidationError("%q", n)
// }
// if fl := g.fleetIndex(ri, n); fl >= 0 {
// return 0, e.NewEntityTypeNameDuplicateError("fleet %w", g.Fleets[fl].Name)
// }
// fleets := slices.Clone(g.Fleets)
// fleets = append(fleets, Fleet{
// ID: uuid.New(),
// OwnerID: g.Race[ri].ID,
// Name: n,
// })
// g.Fleets = fleets
// return len(g.Fleets) - 1, nil
// }
// func (g *Game) joinFleetsInternal(ri int, fleetSourceName, fleetTargetName string) (err error) {
// fiSource := g.fleetIndex(ri, fleetSourceName)
// if fiSource < 0 {
// return e.NewEntityNotExistsError("source fleet %s", fleetSourceName)
// }
// fiTarget := g.fleetIndex(ri, fleetTargetName)
// if fiTarget < 0 {
// return e.NewEntityNotExistsError("target fleet %s", fleetTargetName)
// }
// srcState, planet1, _ := FleetState(g, g.Fleets[fiSource].ID)
// tgtState, planet2, _ := FleetState(g, g.Fleets[fiTarget].ID)
// if srcState != StateInOrbit || srcState != tgtState || *planet1 != *planet2 {
// return e.NewShipsNotOnSamePlanetError()
// }
// for sgi, sg := range g.listIndexShipGroups(ri) {
// if sg.FleetID != nil && *sg.FleetID == g.Fleets[fiSource].ID {
// g.ShipGroups[sgi].FleetID = &g.Fleets[fiTarget].ID
// }
// }
// return g.deleteFleetSafe(ri, fleetSourceName)
// }
// func (g *Game) deleteFleetSafe(ri int, name string) error {
// fi := g.fleetIndex(ri, name)
// if fi < 0 {
// return e.NewEntityNotExistsError("fleet %s", name)
// }
// for sgi := range g.ShipGroups {
// if g.ShipGroups[sgi].FleetID != nil && *g.ShipGroups[sgi].FleetID == g.Fleets[fi].ID {
// return e.NewEntityInUseError("fleet %s: race %s, group #%d", name, g.Race[ri].Name, g.ShipGroups[sgi].Number)
// }
// }
// g.Fleets = append(g.Fleets[:fi], g.Fleets[fi+1:]...)
// return nil
// }
// func (g Game) fleetIndex(ri int, name string) int {
// return slices.IndexFunc(g.Fleets, func(f Fleet) bool { return f.OwnerID == g.Race[ri].ID && f.Name == name })
// }
// func (g Game) listFleets(ri int) iter.Seq[Fleet] {
// return func(yield func(Fleet) bool) {
// for _, fl := range g.listIndexFleets(ri) {
// if !yield(fl) {
// return
// }
// }
// }
// }
// func (g Game) listIndexFleets(ri int) iter.Seq2[int, Fleet] {
// return func(yield func(int, Fleet) bool) {
// for i := range g.Fleets {
// if g.Fleets[i].OwnerID == g.Race[ri].ID {
// if !yield(i, g.Fleets[i]) {
// return
// }
// }
// }
// }
// }
// func FleetGroups(g *Game, ri, fi int) iter.Seq[ShipGroup] {
// if len(g.Fleets) < fi+1 {
// panic(fmt.Sprintf("FleetGroups: game fleets index %d invalid: len=%d", fi, len(g.Fleets)))
// }
// return func(yield func(ShipGroup) bool) {
// for sg := range g.listShipGroups(ri) {
// if sg.FleetID != nil && *sg.FleetID == g.Fleets[fi].ID {
// if !yield(sg) {
// break
// }
// }
// }
// }
// }
-44
View File
@@ -3,13 +3,9 @@ package game
import (
"encoding/json"
"fmt"
"iter"
"maps"
"slices"
"strings"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
)
type TechSet map[Tech]float64
@@ -48,46 +44,6 @@ func (g Game) Votes(raceID uuid.UUID) float64 {
return pop / 1000.
}
func (g Game) PlanetByNumber(number uint) (Planet, error) {
pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == number })
if pi < 0 {
return Planet{}, e.NewGameStateError("PlanetByNumber: planet with number=%d not found", number)
}
return g.Map.Planet[pi], nil
}
func (g Game) ShipsInUpgrade(planetNumber uint) iter.Seq[ShipGroup] {
return func(yield func(ShipGroup) bool) {
for sg := range g.ShipGroups {
if g.ShipGroups[sg].Destination == planetNumber && g.ShipGroups[sg].State() == StateUpgrade {
if !yield(g.ShipGroups[sg]) {
break
}
}
}
}
}
func (g Game) raceIndex(name string) (int, error) {
i := slices.IndexFunc(g.Race, func(r Race) bool { return r.Name == name })
if i < 0 {
return i, e.NewRaceUnknownError(name)
}
return i, nil
}
// -----------------------------------------------------------------------------
// validateTypeName always return v without leading and trailing spaces
func validateTypeName(v string) (string, bool) {
s := strings.TrimSpace(v)
if len(s) > 0 {
return s, true
}
// TODO: special symbols AND include error check in all user-input test
return s, false
}
func (g Game) MarshalBinary() (data []byte, err error) {
return json.Marshal(&g)
}
+19 -19
View File
@@ -1,25 +1,25 @@
package game
import "iter"
// import "iter"
func (g *Game) CreateShips(ri int, shipTypeName string, planetNumber uint, quantity int) error {
return nil // g.createShips(ri, shipTypeName, int(planetNumber), quantity)
}
// func (g *Game) CreateShips(ri int, shipTypeName string, planetNumber uint, quantity int) error {
// return nil // g.createShips(ri, shipTypeName, int(planetNumber), quantity)
// }
func (g Game) ListShipGroups(ri int) iter.Seq[ShipGroup] {
return func(yield func(ShipGroup) bool) {}
// return g.listShipGroups(ri)
}
// func (g Game) ListShipGroups(ri int) iter.Seq[ShipGroup] {
// return func(yield func(ShipGroup) bool) {}
// // return g.listShipGroups(ri)
// }
func (g Game) ListFleets(ri int) iter.Seq[Fleet] {
return func(yield func(Fleet) bool) {}
// return g.listFleets(ri)
}
// func (g Game) ListFleets(ri int) iter.Seq[Fleet] {
// return func(yield func(Fleet) bool) {}
// // return g.listFleets(ri)
// }
func (g Game) MustPlanetByNumber(num uint) Planet {
p, err := g.PlanetByNumber(num)
if err != nil {
panic(err)
}
return p
}
// func (g Game) MustPlanetByNumber(num uint) Planet {
// p, err := g.PlanetByNumber(num)
// if err != nil {
// panic(err)
// }
// return p
// }
+41 -88
View File
@@ -1,101 +1,54 @@
package game_test
import (
"fmt"
"testing"
"github.com/google/uuid"
"github.com/iliadenisov/galaxy/internal/controller"
"github.com/iliadenisov/galaxy/internal/model/game"
"github.com/stretchr/testify/assert"
)
var (
Race_0 = game.Race{
ID: Race_0_ID,
Vote: Race_0_ID,
Name: "Race_0",
Tech: map[game.Tech]float64{
game.TechDrive: 1.1,
game.TechWeapons: 1.2,
game.TechShields: 1.3,
game.TechCargo: 1.4,
},
Relations: []game.RaceRelation{{RaceID: Race_1_ID, Relation: game.RelationWar}},
}
Race_1 = game.Race{
ID: Race_1_ID,
Vote: Race_1_ID,
Name: "Race_1",
Tech: map[game.Tech]float64{
game.TechDrive: 2.1,
game.TechWeapons: 2.2,
game.TechShields: 2.3,
game.TechCargo: 2.4,
},
Relations: []game.RaceRelation{{RaceID: Race_0_ID, Relation: game.RelationPeace}},
}
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))
Race_0_ID = uuid.New()
Race_0_idx = 0
Race_0_Gunship = "R0_Gunship"
Race_0_Freighter = "R0_Freighter"
R0_Planet_0_num uint = 0
R0_Planet_2_num uint = 2
Race_0_Gunship_idx = 0
Race_0_Freighter_idx = 1
Race_0_Cruiser_idx = 2
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))
Race_1_ID = uuid.New()
Race_1_idx = 1
Race_1_Gunship = "R1_Gunship"
Race_1_Freighter = "R1_Freighter"
R1_Planet_1_num uint = 1
Race_1_Gunship_idx = 0
Race_1_Freighter_idx = 1
Race_1_Cruiser_idx = 2
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))
ShipType_Cruiser = "Cruiser"
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))
Cruiser = game.ShipType{
ShipTypeReport: game.ShipTypeReport{
Name: "Cruiser",
Drive: 15,
Armament: 1,
Weapons: 15,
Shields: 15,
Cargo: 0,
},
}
)
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))
func assertNoError(err error) {
if err != nil {
panic(fmt.Sprintf("init assertion failed: %v", err))
}
}
func newGame() *game.Game {
g := &game.Game{
Race: []game.Race{
Race_0,
Race_1,
},
Map: game.Map{
Width: 1000,
Height: 1000,
Planet: []game.Planet{
controller.NewPlanet(R0_Planet_0_num, "Planet_0", Race_0.ID, 0, 0, 100, 100, 100, 0, game.ProductionNone.AsType(uuid.Nil)),
controller.NewPlanet(R1_Planet_1_num, "Planet_1", Race_1.ID, 1, 1, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
controller.NewPlanet(R0_Planet_2_num, "Planet_2", Race_0.ID, 2, 2, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
controller.NewPlanet(3, "Planet_3", uuid.Nil, 500, 500, 100, 0, 0, 0, game.ProductionNone.AsType(uuid.Nil)),
},
},
}
// assertNoError(g.CreateShipType(Race_0.Name, Race_0_Gunship, 60, 30, 100, 0, 3))
// assertNoError(g.CreateShipType(Race_0.Name, Race_0_Freighter, 8, 0, 2, 10, 0))
// assertNoError(g.CreateShipType(Race_0.Name, ShipType_Cruiser, Cruiser.Drive, Cruiser.Weapons, Cruiser.Shields, Cruiser.Cargo, int(Cruiser.Armament)))
// assertNoError(g.CreateShipType(Race_1.Name, Race_1_Gunship, 60, 30, 100, 0, 3))
// assertNoError(g.CreateShipType(Race_1.Name, Race_1_Freighter, 8, 0, 2, 10, 0))
// assertNoError(g.CreateShipType(Race_1.Name, ShipType_Cruiser, 15, 15, 15, 0, 2)) // same name - different type (why.)
return g
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))
}
-514
View File
@@ -229,517 +229,3 @@ func (sg ShipGroup) BombingPower(st *ShipType) float64 {
float64(sg.Number)
return number.Fixed3(result)
}
// JoinEqualGroups iterates over all races and joins their respective equal ship groups.
// Used in turn production.
// func JoinEqualGroups(g *Game) {
// // for i := range g.Race {
// // g.joinEqualGroupsInternal(i)
// // }
// }
// func (g *Game) JoinEqualGroups(raceName string) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// g.joinEqualGroupsInternal(ri)
// return nil
// }
// func (g *Game) joinEqualGroupsInternal(ri int) {
// shipGroups := slices.Collect(maps.Values(maps.Collect(g.listIndexShipGroups(ri))))
// origin := len(shipGroups)
// if origin < 2 {
// return
// }
// for i := 0; i < len(shipGroups)-1; i++ {
// for j := len(shipGroups) - 1; j > i; j-- {
// if shipGroups[i].Equal(shipGroups[j]) {
// shipGroups[i].Index = maxUint(shipGroups[i].Index, shipGroups[j].Index)
// shipGroups[i].Number += shipGroups[j].Number
// shipGroups = append(shipGroups[:j], shipGroups[j+1:]...)
// }
// }
// }
// if len(shipGroups) == origin {
// return
// }
// g.ShipGroups = slices.DeleteFunc(g.ShipGroups, func(v ShipGroup) bool { return v.OwnerID == g.Race[ri].ID })
// g.ShipGroups = append(g.ShipGroups, shipGroups...)
// }
// func (g *Game) BreakGroup(raceName string, groupIndex, quantity uint) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// return g.breakGroupInternal(ri, groupIndex, quantity)
// }
// func (g *Game) DisassembleGroup(raceName string, groupIndex, quantity uint) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// return g.disassembleGroupInternal(ri, groupIndex, quantity)
// }
// func (g *Game) disassembleGroupInternal(ri int, groupIndex, quantity uint) error {
// sgi := -1
// var maxIndex uint
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// if sg.Index > maxIndex {
// maxIndex = sg.Index
// }
// }
// if sgi < 0 {
// return e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// if g.ShipGroups[sgi].State() != StateInOrbit {
// return e.NewShipsBusyError()
// }
// if g.ShipGroups[sgi].Number < quantity {
// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
// }
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
// if pl < 0 {
// return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination)
// }
// var sti int
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
// // hard to test, need manual game data invalidation
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
// }
// if quantity > 0 && quantity < g.ShipGroups[sgi].Number {
// // make new group for disassembly
// nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity)
// if err != nil {
// return err
// }
// sgi = nsgi
// }
// if g.ShipGroups[sgi].CargoType != nil {
// ct := *g.ShipGroups[sgi].CargoType
// load := g.ShipGroups[sgi].Load
// switch ct {
// case CargoColonist:
// if g.Map.Planet[pl].Owner == g.Race[ri].ID {
// g.Map.Planet[pl] = UnloadColonists(g.Map.Planet[pl], load)
// }
// case CargoMaterial:
// g.Map.Planet[pl].Material += load
// case CargoCapital:
// g.Map.Planet[pl].Capital += load
// }
// }
// g.Map.Planet[pl].Material += g.ShipGroups[sgi].EmptyMass(&g.Race[ri].ShipTypes[sti])
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
// return nil
// }
// func (g *Game) LoadCargo(raceName string, groupIndex uint, cargoType string, ships uint, quantity float64) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// ct, ok := CargoTypeSet[cargoType]
// if !ok {
// return e.NewCargoTypeInvalidError(cargoType)
// }
// return g.loadCargoInternal(ri, groupIndex, ct, ships, quantity)
// }
// func (g *Game) UnloadCargo(raceName string, groupIndex uint, ships uint, quantity float64) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// return g.unloadCargoInternal(ri, groupIndex, ships, quantity)
// }
// // Промышленность и Сырье могут быть выгружены на любой планете.
// // Колонисты могут быть высажены только на планеты, принадлежащие Вам или на необитаемые планеты.
// func (g *Game) unloadCargoInternal(ri int, groupIndex uint, ships uint, quantity float64) error {
// if ships == 0 && quantity > 0 {
// return e.NewCargoQuantityWithoutGroupBreakError()
// }
// sgi := -1
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// }
// if sgi < 0 {
// return e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// if g.ShipGroups[sgi].State() != StateInOrbit {
// return e.NewShipsBusyError()
// }
// var sti int
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
// // hard to test, need manual game data invalidation
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
// }
// if g.Race[ri].ShipTypes[sti].Cargo < 1 {
// return e.NewNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name)
// }
// if g.ShipGroups[sgi].CargoType == nil || g.ShipGroups[sgi].Load == 0 {
// return e.NewCargoUnloadEmptyError()
// }
// ct := *g.ShipGroups[sgi].CargoType
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
// if pl < 0 {
// return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination)
// }
// if ct == CargoColonist {
// if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID {
// return e.NewEntityNotOwnedError("planet #%d unload %v", g.Map.Planet[pl].Number, ct)
// }
// if g.Map.Planet[pl].Owner == uuid.Nil {
// g.Map.Planet[pl].Owner = g.Race[ri].ID
// }
// }
// var availableOnPlanet *float64
// switch ct {
// case CargoMaterial:
// availableOnPlanet = &g.Map.Planet[pl].Material
// case CargoCapital:
// availableOnPlanet = &g.Map.Planet[pl].Capital
// case CargoColonist:
// availableOnPlanet = &g.Map.Planet[pl].Colonists
// default:
// return e.NewGameStateError("CargoType not accepted: %v", ct)
// }
// if ships > 0 && ships < g.ShipGroups[sgi].Number {
// nsgi, err := g.breakGroupSafe(ri, groupIndex, ships)
// if err != nil {
// return err
// }
// sgi = nsgi
// }
// toBeUnloaded := quantity
// if quantity == 0 {
// toBeUnloaded = g.ShipGroups[sgi].Load
// }
// if toBeUnloaded > g.ShipGroups[sgi].Load {
// return e.NewCargoUnoadNotEnoughError("load: %.03f", g.ShipGroups[sgi].Load)
// }
// *availableOnPlanet += toBeUnloaded
// g.ShipGroups[sgi].Load -= toBeUnloaded
// if g.ShipGroups[sgi].Load == 0 {
// g.ShipGroups[sgi].CargoType = nil
// }
// return nil
// }
// // Корабль может нести только один тип груза одновременно.
// // Возможные типы груза - это колонисты, сырье и промышленность.
// // Груз может быть доставлен на борт корабля с Вашей или не занятой планеты, на которой он имеется.
// func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, ships uint, quantity float64) error {
// if ships == 0 && quantity > 0 {
// return e.NewCargoQuantityWithoutGroupBreakError()
// }
// sgi := -1
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// }
// if sgi < 0 {
// return e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// if g.ShipGroups[sgi].State() != StateInOrbit {
// return e.NewShipsBusyError()
// }
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
// if pl < 0 {
// return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination)
// }
// if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID {
// return e.NewEntityNotOwnedError("planet #%d", g.Map.Planet[pl].Number)
// }
// var sti int
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
// // hard to test, need manual game data invalidation
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
// }
// if g.Race[ri].ShipTypes[sti].Cargo < 1 {
// return e.NewNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name)
// }
// if g.ShipGroups[sgi].CargoType != nil && *g.ShipGroups[sgi].CargoType != ct {
// return e.NewCargoLoadNotEqualError("cargo: %v", *g.ShipGroups[sgi].CargoType)
// }
// if ships > 0 && ships < g.ShipGroups[sgi].Number {
// nsgi, err := g.breakGroupSafe(ri, groupIndex, ships)
// if err != nil {
// return err
// }
// sgi = nsgi
// }
// capacity := g.ShipGroups[sgi].CargoCapacity(&g.Race[ri].ShipTypes[sti])
// freeShipGroupCargoLoad := capacity - g.ShipGroups[sgi].Load
// if freeShipGroupCargoLoad == 0 {
// return e.NewCargoLoadNoSpaceLeftError()
// }
// var availableOnPlanet *float64
// switch ct {
// case CargoMaterial:
// availableOnPlanet = &g.Map.Planet[pl].Material
// case CargoCapital:
// availableOnPlanet = &g.Map.Planet[pl].Capital
// case CargoColonist:
// availableOnPlanet = &g.Map.Planet[pl].Colonists
// default:
// return e.NewGameStateError("CargoType not accepted: %v", ct)
// }
// if quantity > *availableOnPlanet || *availableOnPlanet == 0 {
// return e.NewCargoLoadNotEnoughError("planet: #%d, %s=%.03f", g.Map.Planet[pl].Number, ct, *availableOnPlanet)
// }
// toBeLoaded := quantity
// if quantity == 0 {
// toBeLoaded = *availableOnPlanet
// }
// if toBeLoaded > freeShipGroupCargoLoad {
// toBeLoaded = freeShipGroupCargoLoad
// }
// *availableOnPlanet = *availableOnPlanet - toBeLoaded
// g.ShipGroups[sgi].Load += toBeLoaded
// if g.ShipGroups[sgi].Load > 0 {
// g.ShipGroups[sgi].CargoType = &ct
// }
// return nil
// }
// func (g *Game) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// riAccept, err := g.raceIndex(raceAcceptor)
// if err != nil {
// return err
// }
// return g.giveawayGroupInternal(ri, riAccept, groupIndex, quantity)
// }
// func (g *Game) giveawayGroupInternal(ri, riAccept int, groupIndex, quantity uint) (err error) {
// if ri == riAccept {
// return e.NewSameRaceError(g.Race[riAccept].Name)
// }
// sgi := -1
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// }
// if sgi < 0 {
// return e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// if g.ShipGroups[sgi].Number < quantity {
// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
// }
// var sti int
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
// // hard to test, need manual game data invalidation
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
// }
// var stAcc int
// if stAcc = slices.IndexFunc(g.Race[riAccept].ShipTypes, func(st ShipType) bool { return st.Name == g.Race[ri].ShipTypes[sti].Name }); stAcc >= 0 &&
// !g.Race[ri].ShipTypes[sti].Equal(g.Race[riAccept].ShipTypes[stAcc]) {
// return e.NewGiveawayGroupShipsTypeNotEqualError("race %w, ship type %w", g.Race[riAccept].Name, g.Race[riAccept].ShipTypes[stAcc].Name)
// }
// if stAcc < 0 {
// stAcc, err = g.createShipTypeInternal(riAccept,
// g.Race[ri].ShipTypes[sti].Name,
// g.Race[ri].ShipTypes[sti].Drive,
// g.Race[ri].ShipTypes[sti].Weapons,
// g.Race[ri].ShipTypes[sti].Shields,
// g.Race[ri].ShipTypes[sti].Cargo,
// int(g.Race[ri].ShipTypes[sti].Armament))
// if err != nil {
// return err
// }
// }
// var maxIndex uint
// for sg := range g.listShipGroups(riAccept) {
// if sg.Index > maxIndex {
// maxIndex = sg.Index
// }
// }
// g.ShipGroups = append(g.ShipGroups, ShipGroup{
// Index: maxIndex + 1,
// OwnerID: g.Race[riAccept].ID,
// TypeID: g.Race[riAccept].ShipTypes[stAcc].ID,
// Number: uint(quantity),
// CargoType: g.ShipGroups[sgi].CargoType,
// Load: g.ShipGroups[sgi].Load,
// Tech: maps.Clone(g.ShipGroups[sgi].Tech),
// Destination: g.ShipGroups[sgi].Destination,
// StateInSpace: g.ShipGroups[sgi].StateInSpace,
// StateUpgrade: g.ShipGroups[sgi].StateUpgrade,
// })
// if quantity == 0 || quantity == g.ShipGroups[sgi].Number {
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
// } else {
// g.ShipGroups[sgi].Number -= quantity
// }
// return nil
// }
// func (g *Game) breakGroupInternal(ri int, groupIndex, quantity uint) error {
// sgi := -1
// var maxIndex uint
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// if sg.Index > maxIndex {
// maxIndex = sg.Index
// }
// }
// if sgi < 0 {
// return e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// if g.ShipGroups[sgi].State() != StateInOrbit {
// return e.NewShipsBusyError()
// }
// if g.ShipGroups[sgi].Number < quantity {
// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
// }
// if quantity == 0 || quantity == g.ShipGroups[sgi].Number {
// g.ShipGroups[sgi].FleetID = nil
// } else {
// if _, err := g.breakGroupSafe(ri, groupIndex, quantity); err != nil {
// return err
// }
// }
// return nil
// }
// func (g *Game) breakGroupSafe(ri int, groupIndex uint, newGroupShips uint) (int, error) {
// sgi := -1
// var maxIndex uint
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// if sg.Index > maxIndex {
// maxIndex = sg.Index
// }
// }
// if sgi < 0 {
// return -1, e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// if g.ShipGroups[sgi].Number < newGroupShips {
// return -1, e.NewBreakGroupIllegalNumberError("group #%d ships: %d -> %d", g.ShipGroups[sgi].Index, g.ShipGroups[sgi].Number, newGroupShips)
// }
// newGroup := g.ShipGroups[sgi]
// if g.ShipGroups[sgi].CargoType != nil {
// newGroup.Load = g.ShipGroups[sgi].Load / float64(g.ShipGroups[sgi].Number) * float64(newGroupShips)
// g.ShipGroups[sgi].Load -= newGroup.Load
// }
// newGroup.Number = newGroupShips
// g.ShipGroups[sgi].Number -= newGroup.Number
// newGroup.Index = maxIndex + 1
// newGroup.FleetID = nil
// g.ShipGroups = append(g.ShipGroups, newGroup)
// return len(g.ShipGroups) - 1, nil
// }
// func (g *Game) createShips(ri int, shipTypeName string, planetNumber int, quantity int) error {
// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == shipTypeName })
// if st < 0 {
// return e.NewEntityNotExistsError("ship type %w", shipTypeName)
// }
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(planetNumber) })
// if pl < 0 {
// return e.NewEntityNotExistsError("planet #%d", planetNumber)
// }
// if g.Map.Planet[pl].Owner != g.Race[ri].ID {
// return e.NewEntityNotOwnedError("planet #%d", planetNumber)
// }
// var maxIndex uint
// for _, sg := range g.listIndexShipGroups(ri) {
// if sg.Index > maxIndex {
// maxIndex = sg.Index
// }
// }
// g.ShipGroups = append(g.ShipGroups, ShipGroup{
// Index: maxIndex + 1,
// OwnerID: g.Race[ri].ID,
// TypeID: g.Race[ri].ShipTypes[st].ID,
// Destination: g.Map.Planet[pl].Number,
// Number: uint(quantity),
// Tech: map[Tech]float64{
// TechDrive: g.Race[ri].TechLevel(TechDrive),
// TechWeapons: g.Race[ri].TechLevel(TechWeapons),
// TechShields: g.Race[ri].TechLevel(TechShields),
// TechCargo: g.Race[ri].TechLevel(TechCargo),
// },
// })
// return nil
// }
// func (g Game) listShipGroups(ri int) iter.Seq[ShipGroup] {
// return func(yield func(ShipGroup) bool) {
// for _, sg := range g.listIndexShipGroups(ri) {
// if !yield(sg) {
// return
// }
// }
// }
// }
// func (g Game) listIndexShipGroups(ri int) iter.Seq2[int, ShipGroup] {
// return func(yield func(int, ShipGroup) bool) {
// for i := range g.ShipGroups {
// if g.ShipGroups[i].OwnerID == g.Race[ri].ID {
// if !yield(i, g.ShipGroups[i]) {
// return
// }
// }
// }
// }
// }
// func MustShipGroup(g *Game, ri int, index uint) ShipGroup {
// for sg := range g.listShipGroups(ri) {
// if sg.Index == index {
// return sg
// }
// }
// panic(fmt.Sprintf("race i=%d have no group i=%d", ri, index))
// }
// func maxUint(a, b uint) uint {
// if b > a {
// return b
// }
// return a
// }
-93
View File
@@ -1,93 +0,0 @@
package game
// import (
// "slices"
// e "github.com/iliadenisov/galaxy/internal/error"
// "github.com/iliadenisov/galaxy/internal/util"
// )
// func (g *Game) SendGroup(raceName string, groupIndex, planetNumber, quantity uint) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// return g.sendGroupInternal(ri, groupIndex, planetNumber, quantity)
// }
// func (g *Game) sendGroupInternal(ri int, groupIndex, planetNumber, quantity uint) error {
// sgi := -1
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// }
// if sgi < 0 {
// return e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// var sti int
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
// // hard to test, need manual game data invalidation
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
// }
// st := g.Race[ri].ShipTypes[sti]
// if st.DriveBlockMass() == 0 {
// return e.NewSendShipHasNoDrivesError()
// }
// sourcePlanet, ok := g.ShipGroups[sgi].OnPlanet()
// if !ok {
// return e.NewShipsBusyError()
// }
// if g.ShipGroups[sgi].Number < quantity {
// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
// }
// p1, ok := PlanetByNum(g, sourcePlanet)
// if !ok {
// return e.NewGameStateError("source planet #%d does not exists", sourcePlanet)
// }
// p2, ok := PlanetByNum(g, planetNumber)
// if !ok {
// return e.NewEntityNotExistsError("destination planet #%d", planetNumber)
// }
// rangeToDestination := util.ShortDistance(g.Map.Width, g.Map.Height, p1.X, p1.Y, p2.X, p2.Y)
// if rangeToDestination > g.Race[ri].FlightDistance() {
// return e.NewSendUnreachableDestinationError("range=%.03f", rangeToDestination)
// }
// if quantity > 0 && quantity < g.ShipGroups[sgi].Number {
// nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity)
// if err != nil {
// return err
// }
// sgi = nsgi
// }
// if sourcePlanet == planetNumber {
// g.ShipGroups[sgi] = UnsendShips(g.ShipGroups[sgi])
// g.joinEqualGroupsInternal(ri)
// return nil
// }
// g.ShipGroups[sgi] = LaunchShips(g.ShipGroups[sgi], planetNumber)
// return nil
// }
// func LaunchShips(sg ShipGroup, destination uint) ShipGroup {
// sg.StateInSpace = &InSpace{
// Origin: sg.Destination,
// }
// sg.Destination = destination
// return sg
// }
// func UnsendShips(sg ShipGroup) ShipGroup {
// sg.Destination = sg.StateInSpace.Origin
// sg.StateInSpace = nil
// return sg
// }
-69
View File
@@ -1,69 +0,0 @@
package game_test
// import (
// "slices"
// "testing"
// e "github.com/iliadenisov/galaxy/internal/error"
// "github.com/iliadenisov/galaxy/internal/model/game"
// "github.com/stretchr/testify/assert"
// )
// func TestSendGroup(t *testing.T) {
// g := newGame()
// // group #1 - in_orbit, free to upgrade
// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 10))
// // group #2 - in_space
// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
// g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
// // group #3 - in_orbit, unmovable
// g.CreateShipType(Race_0.Name, "Fortress", 0, 50, 30, 100, 0)
// assert.NoError(t, g.CreateShips(Race_0_idx, "Fortress", R0_Planet_0_num, 1))
// assert.ErrorContains(t,
// g.SendGroup("UnknownRace", 1, 2, 0),
// e.GenericErrorText(e.ErrInputUnknownRace))
// assert.ErrorContains(t,
// g.SendGroup(Race_0.Name, 555, 2, 0),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.SendGroup(Race_0.Name, 1, 222, 0),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.SendGroup(Race_0.Name, 2, 1, 0),
// e.GenericErrorText(e.ErrShipsBusy))
// assert.ErrorContains(t,
// g.SendGroup(Race_0.Name, 3, 2, 0),
// e.GenericErrorText(e.ErrSendShipHasNoDrives))
// assert.ErrorContains(t,
// g.SendGroup(Race_0.Name, 1, 2, 100),
// e.GenericErrorText(e.ErrBeakGroupNumberNotEnough))
// assert.ErrorContains(t,
// g.SendGroup(Race_0.Name, 1, 3, 0),
// e.GenericErrorText(e.ErrSendUnreachableDestination))
// assert.NoError(t, g.SendGroup(Race_0.Name, 1, 2, 3)) // send 3 of 10
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
// assert.Equal(t, uint(7), g.ShipGroups[0].Number)
// assert.Equal(t, game.StateInOrbit, g.ShipGroups[0].State())
// assert.Equal(t, uint(3), g.ShipGroups[3].Number)
// assert.Equal(t, game.StateLaunched, g.ShipGroups[3].State())
// assert.NoError(t, g.SendGroup(Race_0.Name, 4, 0, 2)) // un-send 2 of 3
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
// assert.Equal(t, uint(9), game.MustShipGroup(g, Race_0_idx, 5).Number)
// assert.Equal(t, game.StateInOrbit, game.MustShipGroup(g, Race_0_idx, 5).State())
// assert.Equal(t, uint(1), game.MustShipGroup(g, Race_0_idx, 4).Number)
// assert.Equal(t, game.StateLaunched, game.MustShipGroup(g, Race_0_idx, 4).State())
// assert.NoError(t, g.SendGroup(Race_0.Name, 4, 0, 0)) // un-send the rest 1
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3)
// assert.Equal(t, uint(10), game.MustShipGroup(g, Race_0_idx, 5).Number)
// assert.Equal(t, game.StateInOrbit, game.MustShipGroup(g, Race_0_idx, 5).State())
// assert.NoError(t, g.SendGroup(Race_0.Name, 5, 2, 0))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3)
// assert.Equal(t, uint(10), game.MustShipGroup(g, Race_0_idx, 5).Number)
// assert.Equal(t, game.StateLaunched, game.MustShipGroup(g, Race_0_idx, 5).State())
// }
-515
View File
@@ -255,518 +255,3 @@ func TestShipGroupEqual(t *testing.T) {
left.Load = right.Load / float64(right.Number) * float64(left.Number)
assert.True(t, left.Equal(right))
}
// func TestCreateShips(t *testing.T) {
// g := newGame()
// assert.ErrorContains(t,
// g.CreateShips(Race_0_idx, "Unknown_Ship_Type", R0_Planet_0_num, 2),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.CreateShips(Race_0_idx, Race_0_Gunship, R1_Planet_1_num, 2),
// e.GenericErrorText(e.ErrInputEntityNotOwned))
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 1)
// assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1))
// assert.Len(t, slices.Collect(g.ListShipGroups(1)), 1)
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 6))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 2)
// assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 1))
// assert.Len(t, slices.Collect(g.ListShipGroups(1)), 2)
// }
// func TestJoinEqualGroups(t *testing.T) {
// g := newGame()
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // 1 -> 2
// assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1))
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 6)) // (2)
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2)) // (3)
// assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 1))
// g.Race[Race_0_idx].SetTechLevel(game.TechDrive, 1.5)
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 9)) // 4 -> 6
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) // 5 -> 7
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 4)) // (6)
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 4)) // (7)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
// g.Race[Race_1_idx].SetTechLevel(game.TechShields, 2.0)
// assert.NoError(t, g.CreateShips(1, Race_1_Freighter, R1_Planet_1_num, 1))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 3)
// assert.NoError(t, g.JoinEqualGroups(Race_0.Name))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 3)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
// shipTypeID := func(ri int, name string) uuid.UUID {
// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(v game.ShipType) bool { return v.Name == name })
// if st < 0 {
// t.Fatalf("ShipType not found: %s", name)
// return uuid.Nil
// }
// return g.Race[ri].ShipTypes[st].ID
// }
// for sg := range g.ListShipGroups(Race_0_idx) {
// switch {
// case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Freighter) && sg.TechLevel(game.TechDrive) == 1.1:
// assert.Equal(t, uint(7), sg.Number)
// assert.Equal(t, uint(2), sg.Index)
// case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Freighter) && sg.TechLevel(game.TechDrive) == 1.5:
// assert.Equal(t, uint(11), sg.Number)
// assert.Equal(t, uint(7), sg.Index)
// case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Gunship) && sg.TechLevel(game.TechDrive) == 1.1:
// assert.Equal(t, uint(2), sg.Number)
// assert.Equal(t, uint(3), sg.Index)
// case sg.TypeID == shipTypeID(Race_0_idx, Race_0_Gunship) && sg.TechLevel(game.TechDrive) == 1.5:
// assert.Equal(t, uint(13), sg.Number)
// assert.Equal(t, uint(6), sg.Index)
// default:
// t.Error("not all ship groups covered")
// }
// }
// }
// func TestBreakGroup(t *testing.T) {
// g := newGame()
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 13)) // group #1 (0)
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 7)) // group #2 (1) - In_Space
// g.ShipGroups[1].StateInSpace = &game.InSpace{
// Origin: 1,
// Range: 1,
// }
// fleet := "R0_Fleet"
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleet, 1, 0))
// assert.ErrorContains(t,
// g.BreakGroup("UnknownRace", 1, 0),
// e.GenericErrorText(e.ErrInputUnknownRace))
// assert.ErrorContains(t,
// g.BreakGroup(Race_0.Name, 555, 0),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.BreakGroup(Race_0.Name, 1, 17),
// e.GenericErrorText(e.ErrBeakGroupNumberNotEnough))
// assert.ErrorContains(t,
// g.BreakGroup(Race_0.Name, 2, 0),
// e.GenericErrorText(e.ErrShipsBusy))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 2)
// assert.Len(t, slices.Collect(g.ListFleets(Race_0_idx)), 1)
// // group #1 -> group #3 (5 new, 8 left)
// assert.NoError(t, g.BreakGroup(Race_0.Name, 1, 5)) // group #3 (2)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3)
// assert.Equal(t, uint(8), g.ShipGroups[0].Number)
// assert.NotNil(t, g.ShipGroups[0].FleetID)
// assert.Equal(t, uint(5), g.ShipGroups[2].Number)
// assert.Equal(t, uint(3), g.ShipGroups[2].Index)
// assert.Nil(t, g.ShipGroups[2].FleetID)
// assert.Nil(t, g.ShipGroups[2].CargoType)
// // group #1 -> group #4 (2 new, 6 left)
// g.ShipGroups[0].CargoType = game.CargoColonist.Ref()
// g.ShipGroups[0].Load = 32.8 // 8 ships
// assert.NoError(t, g.BreakGroup(Race_0.Name, 1, 2)) // group #4 (3)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
// assert.Equal(t, uint(6), g.ShipGroups[0].Number)
// assert.NotNil(t, g.ShipGroups[0].FleetID)
// assert.Equal(t, uint(2), g.ShipGroups[3].Number)
// assert.Equal(t, uint(4), g.ShipGroups[3].Index)
// assert.Nil(t, g.ShipGroups[3].FleetID)
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleet, 4, 0))
// assert.NotNil(t, g.ShipGroups[3].FleetID)
// assert.Equal(t, game.CargoColonist.Ref(), g.ShipGroups[0].CargoType)
// assert.Equal(t, 24.6, number.Fixed3(g.ShipGroups[0].Load))
// assert.Equal(t, game.CargoColonist.Ref(), g.ShipGroups[3].CargoType)
// assert.Equal(t, 8.2, number.Fixed3(g.ShipGroups[3].Load))
// // group #1 -> MAX 6 off the fleet
// assert.NoError(t, g.BreakGroup(Race_0.Name, 1, 6)) // group #1 (0)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
// assert.Equal(t, uint(6), g.ShipGroups[0].Number)
// assert.Nil(t, g.ShipGroups[0].FleetID)
// // group #4 -> ALL off the fleet
// assert.NoError(t, g.BreakGroup(Race_0.Name, 4, 0)) // group #1 (0)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
// assert.Equal(t, uint(2), g.ShipGroups[3].Number)
// assert.Nil(t, g.ShipGroups[3].FleetID)
// }
// func TestGiveawayGroup(t *testing.T) {
// g := newGame()
// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 11)) // group #1 (0)
// assert.NoError(t, g.CreateShips(Race_1_idx, ShipType_Cruiser, R1_Planet_1_num, 23)) // group #1 (1)
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 17)) // group #2 (2) - In_Space
// assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, "R0_Fleet", 2, 0))
// assert.NotNil(t, g.ShipGroups[2].FleetID)
// g.ShipGroups[2].StateInSpace = &game.InSpace{
// Origin: 2,
// Range: 31.337,
// }
// g.ShipGroups[2].CargoType = game.CargoMaterial.Ref()
// g.ShipGroups[2].Load = 1.234
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 2)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 1)
// assert.ErrorContains(t,
// g.GiveawayGroup("UnknownRace", Race_1.Name, 2, 0),
// e.GenericErrorText(e.ErrInputUnknownRace))
// assert.ErrorContains(t,
// g.GiveawayGroup(Race_0.Name, "UnknownRace", 2, 0),
// e.GenericErrorText(e.ErrInputUnknownRace))
// assert.ErrorContains(t,
// g.GiveawayGroup(Race_0.Name, Race_0.Name, 2, 0),
// e.GenericErrorText(e.ErrInputSameRace))
// assert.ErrorContains(t,
// g.GiveawayGroup(Race_0.Name, Race_1.Name, 555, 0),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.GiveawayGroup(Race_0.Name, Race_1.Name, 2, 18),
// e.GenericErrorText(e.ErrBeakGroupNumberNotEnough))
// assert.ErrorContains(t,
// g.GiveawayGroup(Race_0.Name, Race_1.Name, 1, 0),
// e.GenericErrorText(e.ErrGiveawayGroupShipsTypeNotEqual))
// assert.NoError(t, g.GiveawayGroup(Race_0.Name, Race_1.Name, 2, 11)) // group #2 (3)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 2)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 2)
// sto := slices.IndexFunc(g.Race[Race_0_idx].ShipTypes, func(st game.ShipType) bool { return st.Name == Race_0_Gunship })
// sti := slices.IndexFunc(g.Race[Race_1_idx].ShipTypes, func(st game.ShipType) bool { return st.Name == Race_0_Gunship })
// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Name, g.Race[Race_0_idx].ShipTypes[sto].Name)
// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Drive, g.Race[Race_0_idx].ShipTypes[sto].Drive)
// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Weapons, g.Race[Race_0_idx].ShipTypes[sto].Weapons)
// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Shields, g.Race[Race_0_idx].ShipTypes[sto].Shields)
// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Cargo, g.Race[Race_0_idx].ShipTypes[sto].Cargo)
// assert.Equal(t, g.Race[Race_1_idx].ShipTypes[sti].Armament, g.Race[Race_0_idx].ShipTypes[sto].Armament)
// assert.Equal(t, g.ShipGroups[2].State(), g.ShipGroups[3].State())
// assert.Equal(t, g.ShipGroups[2].CargoType, g.ShipGroups[3].CargoType)
// assert.Equal(t, g.ShipGroups[2].Load, g.ShipGroups[3].Load)
// assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechDrive), g.ShipGroups[3].TechLevel(game.TechDrive))
// assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechWeapons), g.ShipGroups[3].TechLevel(game.TechWeapons))
// assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechShields), g.ShipGroups[3].TechLevel(game.TechShields))
// assert.Equal(t, g.ShipGroups[2].TechLevel(game.TechCargo), g.ShipGroups[3].TechLevel(game.TechCargo))
// assert.Equal(t, g.ShipGroups[2].Destination, g.ShipGroups[3].Destination)
// assert.Equal(t, g.ShipGroups[2].StateInSpace, g.ShipGroups[3].StateInSpace)
// assert.Equal(t, g.ShipGroups[2].StateUpgrade, g.ShipGroups[3].StateUpgrade)
// assert.Equal(t, g.ShipGroups[3].OwnerID, g.Race[Race_1_idx].ID)
// assert.Equal(t, g.ShipGroups[3].TypeID, g.Race[Race_1_idx].ShipTypes[sti].ID)
// assert.Equal(t, g.ShipGroups[3].Number, uint(11))
// assert.Nil(t, g.ShipGroups[3].FleetID)
// assert.NoError(t, g.GiveawayGroup(Race_1.Name, Race_0.Name, 2, 11))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_1_idx)), 1)
// }
// func TestLoadCargo(t *testing.T) {
// g := newGame()
// // 1: idx = 0 / Ready to load
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
// // 2: idx = 1 / Has no cargo bay
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1))
// // 3: idx = 2 / In_Space
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
// g.ShipGroups[2].StateInSpace = &game.InSpace{
// Origin: 2,
// Range: 31.337,
// }
// // 4: idx = 3 / loaded with COL
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
// g.ShipGroups[3].CargoType = game.CargoColonist.Ref()
// g.ShipGroups[3].Load = 1.234
// // 5: idx = 4 / on foreign planet
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
// g.ShipGroups[4].Destination = R1_Planet_1_num
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 5)
// // tests
// assert.ErrorContains(t,
// g.LoadCargo("UnknownRace", 1, game.CargoMaterial.String(), 0, 0),
// e.GenericErrorText(e.ErrInputUnknownRace))
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 1, "GOLD", 0, 0),
// e.GenericErrorText(e.ErrInputCargoTypeInvalid))
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 555, game.CargoMaterial.String(), 0, 0),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 3, game.CargoMaterial.String(), 0, 0),
// e.GenericErrorText(e.ErrShipsBusy))
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 5, game.CargoMaterial.String(), 0, 0),
// e.GenericErrorText(e.ErrInputEntityNotOwned))
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 2, game.CargoMaterial.String(), 0, 0),
// e.GenericErrorText(e.ErrInputNoCargoBay))
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 4, game.CargoMaterial.String(), 0, 0),
// e.GenericErrorText(e.ErrInputCargoLoadNotEqual))
// // initial planet is empty
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0),
// e.GenericErrorText(e.ErrInputCargoLoadNotEnough))
// // add cargo to planet
// g.Map.Planet[0].Material = 100
// // not enough on the planet
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 101),
// e.GenericErrorText(e.ErrInputCargoLoadNotEnough))
// // quantity > ships
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 1),
// e.GenericErrorText(e.ErrInputCargoQuantityWithoutGroupBreak))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 5)
// // break group and load maximum
// assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 2, 0))
// assert.Equal(t, 58.0, g.Map.Planet[0].Material)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 6)
// assert.Nil(t, g.ShipGroups[0].CargoType)
// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType)
// assert.Equal(t, uint(9), g.ShipGroups[0].Number)
// assert.Equal(t, 0.0, g.ShipGroups[0].Load)
// assert.Equal(t, uint(2), g.ShipGroups[5].Number)
// assert.Equal(t, 42.0, g.ShipGroups[5].Load)
// // break group and load limited
// assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 2, 18))
// assert.Equal(t, 40.0, g.Map.Planet[0].Material)
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
// assert.Nil(t, g.ShipGroups[0].CargoType)
// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[6].CargoType)
// assert.Equal(t, uint(7), g.ShipGroups[0].Number)
// assert.Equal(t, 0.0, g.ShipGroups[0].Load)
// assert.Equal(t, uint(2), g.ShipGroups[6].Number)
// assert.Equal(t, 18.0, g.ShipGroups[6].Load)
// // add cargo to planet
// g.Map.Planet[0].Material = 100
// // loading all available cargo
// assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
// assert.Equal(t, 0.0, g.Map.Planet[0].Material)
// assert.Equal(t, 100.0, g.ShipGroups[0].Load) // free: 131.0
// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[0].CargoType)
// // add cargo to planet
// g.Map.Planet[0].Material = 200
// assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 31))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
// assert.Equal(t, 169.0, g.Map.Planet[0].Material)
// assert.Equal(t, 131.0, g.ShipGroups[0].Load) // free: 100.0
// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[0].CargoType)
// // load to maximum cargo space left
// assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 0))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
// assert.Equal(t, 153.0, g.Map.Planet[0].Material)
// assert.Equal(t, 147.0, g.ShipGroups[0].Load) // free: 0.0
// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[0].CargoType)
// // ship group is full
// assert.ErrorContains(t,
// g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0),
// e.GenericErrorText(e.ErrInputCargoLoadNoSpaceLeft))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
// }
// func TestUnloadCargo(t *testing.T) {
// g := newGame()
// // 1: idx = 0 / empty
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
// // 2: idx = 1 / Has no cargo bay
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1))
// // 3: idx = 2 / In_Space
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
// g.ShipGroups[2].StateInSpace = &game.InSpace{
// Origin: 2,
// Range: 31.337,
// }
// // 4: idx = 3 / loaded with COL
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
// g.ShipGroups[3].CargoType = game.CargoColonist.Ref()
// g.ShipGroups[3].Load = 1.234
// // 5: idx = 4 / on foreign planet / loaded with COL
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
// g.ShipGroups[4].Destination = R1_Planet_1_num
// g.ShipGroups[4].CargoType = game.CargoColonist.Ref()
// g.ShipGroups[4].Load = 1.234
// // 6: idx = 5 / on foreign planet / loaded with MAT
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
// g.ShipGroups[5].Destination = R1_Planet_1_num
// g.ShipGroups[5].CargoType = game.CargoMaterial.Ref()
// g.ShipGroups[5].Load = 100.0
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 6)
// // tests
// assert.ErrorContains(t,
// g.UnloadCargo("UnknownRace", 1, 0, 0),
// e.GenericErrorText(e.ErrInputUnknownRace))
// assert.ErrorContains(t,
// g.UnloadCargo(Race_0.Name, 555, 0, 0),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.UnloadCargo(Race_0.Name, 3, 0, 0),
// e.GenericErrorText(e.ErrShipsBusy))
// assert.ErrorContains(t,
// g.UnloadCargo(Race_0.Name, 2, 0, 0),
// e.GenericErrorText(e.ErrInputNoCargoBay))
// assert.ErrorContains(t,
// g.UnloadCargo(Race_0.Name, 1, 0, 0),
// e.GenericErrorText(e.ErrInputCargoUnloadEmpty))
// assert.ErrorContains(t,
// g.UnloadCargo(Race_0.Name, 5, 0, 0),
// e.GenericErrorText(e.ErrInputEntityNotOwned))
// g.ShipGroups[0].CargoType = game.CargoColonist.Ref()
// g.ShipGroups[0].Load = 100
// assert.ErrorContains(t,
// g.UnloadCargo(Race_0.Name, 1, 11, 101),
// e.GenericErrorText(e.ErrInputCargoUnoadNotEnough))
// assert.ErrorContains(t,
// g.UnloadCargo(Race_0.Name, 1, 0, 1),
// e.GenericErrorText(e.ErrInputCargoQuantityWithoutGroupBreak))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 6)
// // unload MAT on foreign planet / break group
// assert.NoError(t, g.UnloadCargo(Race_0.Name, 6, 3, 0))
// assert.Equal(t, 27.273, number.Fixed3(g.Map.Planet[1].Material))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7)
// assert.Equal(t, uint(3), g.ShipGroups[6].Number)
// assert.Nil(t, g.ShipGroups[6].CargoType)
// assert.Equal(t, 0.0, g.ShipGroups[6].Load)
// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType)
// assert.Equal(t, uint(8), g.ShipGroups[5].Number)
// assert.Equal(t, 72.727, number.Fixed3(g.ShipGroups[5].Load))
// // unload MAT on foreign planet / break group / limited MAT
// assert.NoError(t, g.UnloadCargo(Race_0.Name, 6, 3, 20.0))
// assert.Equal(t, 47.273, number.Fixed3(g.Map.Planet[1].Material))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 8)
// assert.Equal(t, uint(3), g.ShipGroups[7].Number)
// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[7].CargoType)
// assert.Equal(t, 7.273, number.Fixed3(g.ShipGroups[7].Load))
// assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType)
// assert.Equal(t, uint(5), g.ShipGroups[5].Number)
// assert.Equal(t, 45.455, number.Fixed3(g.ShipGroups[5].Load))
// // unload ALL
// assert.NoError(t, g.UnloadCargo(Race_0.Name, 1, 0, 0))
// assert.Equal(t, 100.0, number.Fixed3(g.Map.Planet[0].Colonists))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 8)
// assert.Equal(t, uint(10), g.ShipGroups[0].Number)
// assert.Nil(t, g.ShipGroups[0].CargoType)
// assert.Equal(t, 0.0, number.Fixed3(g.ShipGroups[0].Load))
// }
// func TestDisassembleGroup(t *testing.T) {
// g := newGame()
// // 1: idx = 0 / empty
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
// // 2: idx = 1 / In_Space
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7))
// g.ShipGroups[1].StateInSpace = &game.InSpace{
// Origin: 2,
// Range: 31.337,
// }
// // 3: idx = 2 / loaded with COL
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10))
// g.ShipGroups[2].CargoType = game.CargoColonist.Ref()
// g.ShipGroups[2].Load = 80.0
// // 4: idx = 3 / on foreign planet / loaded with MAT
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
// g.ShipGroups[3].Destination = R1_Planet_1_num
// g.ShipGroups[3].CargoType = game.CargoMaterial.Ref()
// g.ShipGroups[3].Load = 100.0
// // 5: idx = 4 / on foreign planet / loaded with COL
// assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11))
// g.ShipGroups[4].Destination = R1_Planet_1_num
// g.ShipGroups[4].CargoType = game.CargoColonist.Ref()
// g.ShipGroups[4].Load = 2.345
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 5)
// // tests
// assert.ErrorContains(t,
// g.DisassembleGroup("UnknownRace", 1, 0),
// e.GenericErrorText(e.ErrInputUnknownRace))
// assert.ErrorContains(t,
// g.DisassembleGroup(Race_0.Name, 555, 0),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.DisassembleGroup(Race_0.Name, 2, 0),
// e.GenericErrorText(e.ErrShipsBusy))
// assert.ErrorContains(t,
// g.DisassembleGroup(Race_0.Name, 3, 12),
// e.GenericErrorText(e.ErrBeakGroupNumberNotEnough))
// groupEmptyMass := g.ShipGroups[4].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx])
// // groupLoadCOL := g.ShipGroups[4].Load
// planetMAT := g.Map.Planet[1].Material
// planetCOL := g.Map.Planet[1].Colonists
// assert.NoError(t, g.DisassembleGroup(Race_0.Name, 5, 0))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
// assert.Equal(t, planetMAT+groupEmptyMass, g.Map.Planet[1].Material)
// assert.Equal(t, planetCOL, g.Map.Planet[1].Colonists)
// groupEmptyMass = g.ShipGroups[3].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx])
// groupLoadMAT := g.ShipGroups[3].Load
// planetMAT = g.Map.Planet[1].Material
// assert.NoError(t, g.DisassembleGroup(Race_0.Name, 4, 0))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3)
// assert.Equal(t, planetMAT+groupEmptyMass+groupLoadMAT, g.Map.Planet[1].Material)
// groupEmptyMass = g.ShipGroups[2].EmptyMass(&g.Race[Race_0_idx].ShipTypes[Race_0_Freighter_idx])
// planetMAT = g.Map.Planet[0].Material
// planetCOL = g.Map.Planet[0].Colonists
// planetPOP := 90.0
// g.Map.Planet[0].Population = planetPOP
// var shipsDisassembling uint = 3
// groupEmptyMass = groupEmptyMass / float64(g.ShipGroups[2].Number) * float64(shipsDisassembling)
// newGroupUnloadedCOL := g.ShipGroups[2].Load / float64(g.ShipGroups[2].Number) * float64(shipsDisassembling)
// expectPOPIncrease := newGroupUnloadedCOL * 8
// freePOPLeft := g.Map.Planet[0].Size - g.Map.Planet[0].Population
// expectAddedCOL := (expectPOPIncrease - freePOPLeft) / 8
// expectAddedPOP := freePOPLeft
// assert.NoError(t, g.DisassembleGroup(Race_0.Name, 3, shipsDisassembling))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 3)
// assert.Equal(t, planetCOL+expectAddedCOL, g.Map.Planet[0].Colonists)
// assert.Equal(t, planetPOP+expectAddedPOP, g.Map.Planet[0].Population)
// assert.Equal(t, planetMAT+groupEmptyMass, g.Map.Planet[0].Material)
// assert.Equal(t, uint(7), g.ShipGroups[2].Number)
// assert.Equal(t, 56.0, g.ShipGroups[2].Load)
// }
-152
View File
@@ -46,158 +46,6 @@ func GroupUpgradeCost(sg ShipGroup, st ShipType, drive, weapons, shields, cargo
return *uc
}
// func (g *Game) UpgradeGroup(raceName string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// return g.upgradeGroupInternal(ri, groupIndex, techInput, limitShips, limitLevel)
// }
// func (g *Game) upgradeGroupInternal(ri int, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error {
// sgi := -1
// for i, sg := range g.listIndexShipGroups(ri) {
// if sgi < 0 && sg.Index == groupIndex {
// sgi = i
// }
// }
// if sgi < 0 {
// return e.NewEntityNotExistsError("group #%d", groupIndex)
// }
// var sti int
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
// // hard to test, need manual game data invalidation
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
// }
// st := g.Race[ri].ShipTypes[sti]
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
// if pl < 0 {
// return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination)
// }
// if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID {
// return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", g.Map.Planet[pl].Number, groupIndex)
// }
// if g.ShipGroups[sgi].State() != StateInOrbit && g.ShipGroups[sgi].State() != StateUpgrade {
// return e.NewShipsBusyError()
// }
// upgradeValidTech := map[string]Tech{
// TechDrive.String(): TechDrive,
// TechWeapons.String(): TechWeapons,
// TechShields.String(): TechShields,
// TechCargo.String(): TechCargo,
// TechAll.String(): TechAll,
// }
// techRequest, ok := upgradeValidTech[techInput]
// if !ok {
// return e.NewTechUnknownError(techInput)
// }
// var blockMasses map[Tech]float64 = map[Tech]float64{
// TechDrive: st.DriveBlockMass(),
// TechWeapons: st.WeaponsBlockMass(),
// TechShields: st.ShieldsBlockMass(),
// TechCargo: st.CargoBlockMass(),
// }
// switch {
// case techRequest != TechAll && blockMasses[techRequest] == 0:
// return e.NewUpgradeShipTechNotUsedError()
// case techRequest == TechAll && limitLevel != 0:
// return e.NewUpgradeParameterNotAllowedError("tech=%s max_level=%f", techRequest.String(), limitLevel)
// }
// targetLevel := make(map[Tech]float64)
// var sumLevels float64
// for _, tech := range []Tech{TechDrive, TechWeapons, TechShields, TechCargo} {
// if techRequest == TechAll || tech == techRequest {
// if g.Race[ri].TechLevel(tech) < limitLevel {
// return e.NewUpgradeTechLevelInsufficientError("%s=%.03f < %.03f", tech.String(), g.Race[ri].TechLevel(tech), limitLevel)
// }
// targetLevel[tech] = FutureUpgradeLevel(g.Race[ri].TechLevel(tech), g.ShipGroups[sgi].TechLevel(tech), limitLevel)
// } else {
// targetLevel[tech] = CurrentUpgradingLevel(g.ShipGroups[sgi], tech)
// }
// sumLevels += targetLevel[tech]
// }
// productionCapacity := PlanetProductionCapacity(g, g.Map.Planet[pl].Number)
// if g.ShipGroups[sgi].State() == StateUpgrade {
// // to calculate actual capacity we must substract upgrade cost of selected group, if is upgrade state
// productionCapacity -= g.ShipGroups[sgi].StateUpgrade.Cost()
// }
// uc := GroupUpgradeCost(g.ShipGroups[sgi], st, targetLevel[TechDrive], targetLevel[TechWeapons], targetLevel[TechShields], targetLevel[TechCargo])
// costForShip := uc.UpgradeCost(1)
// if costForShip == 0 {
// return e.NewUpgradeShipsAlreadyUpToDateError("%#v", targetLevel)
// }
// shipsToUpgrade := g.ShipGroups[sgi].Number
// // НЕ БОЛЕЕ УКАЗАННОГО
// if limitShips > 0 && shipsToUpgrade > limitShips {
// shipsToUpgrade = limitShips
// }
// maxUpgradableShips := uc.UpgradeMaxShips(productionCapacity)
// /*
// 1. считаем стоимость модернизации одного корабля
// 2. считаем сколько кораблей можно модернизировать
// 3. если не хватает даже на 1 корабль, ограничиваемся одним кораблём и пересчитываем коэффициент пропорционально массе блоков
// 4. иначе, считаем истинное количество кораблей с учётом ограничения maxShips
// */
// blockMassSum := st.EmptyMass()
// coef := productionCapacity / costForShip
// if maxUpgradableShips == 0 {
// if limitLevel > 0 {
// return e.NewUpgradeInsufficientResourcesError("ship cost=%.03f L=%.03f", costForShip, productionCapacity)
// }
// sumLevels = sumLevels * coef
// for tech := range targetLevel {
// if blockMasses[tech] > 0 {
// proportional := sumLevels * (blockMasses[tech] / blockMassSum)
// targetLevel[tech] = proportional
// }
// }
// maxUpgradableShips = 1
// } else if maxUpgradableShips > shipsToUpgrade {
// maxUpgradableShips = shipsToUpgrade
// }
// // sanity check
// uc = GroupUpgradeCost(g.ShipGroups[sgi], st, targetLevel[TechDrive], targetLevel[TechWeapons], targetLevel[TechShields], targetLevel[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 < g.ShipGroups[sgi].Number {
// if g.ShipGroups[sgi].State() == StateUpgrade {
// return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", g.ShipGroups[sgi].Number, maxUpgradableShips)
// }
// nsgi, err := g.breakGroupSafe(ri, groupIndex, maxUpgradableShips)
// if err != nil {
// return err
// }
// sgi = nsgi
// }
// // finally, fill group upgrade prefs
// for tech := range targetLevel {
// if targetLevel[tech] > 0 {
// g.ShipGroups[sgi] = UpgradeGroupPreference(g.ShipGroups[sgi], st, tech, targetLevel[tech])
// }
// }
// return nil
// }
func CurrentUpgradingLevel(sg ShipGroup, tech Tech) float64 {
if sg.StateUpgrade == nil {
return 0
+13 -57
View File
@@ -7,6 +7,19 @@ import (
"github.com/stretchr/testify/assert"
)
var (
Cruiser = game.ShipType{
ShipTypeReport: game.ShipTypeReport{
Name: "Cruiser",
Drive: 15,
Armament: 1,
Weapons: 15,
Shields: 15,
Cargo: 0,
},
}
)
func TestBlockUpgradeCost(t *testing.T) {
assert.Equal(t, 00.0, game.BlockUpgradeCost(1, 1.0, 1.0))
assert.Equal(t, 25.0, game.BlockUpgradeCost(5, 1.0, 2.0))
@@ -108,60 +121,3 @@ func TestUpgradeGroupPreference(t *testing.T) {
assert.Equal(t, 0., sg.StateUpgrade.TechCost(game.TechCargo))
assert.Equal(t, 900., sg.StateUpgrade.Cost())
}
// func TestUpgradeGroup(t *testing.T) {
// g := newGame()
// // group #1 - in_orbit, free to upgrade
// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 10))
// // group #2 - in_space
// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
// g.ShipGroups[1].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23}
// // group #3 - in_orbit, foreign planet
// assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
// g.ShipGroups[2].Destination = R1_Planet_1_num
// assert.ErrorContains(t,
// g.UpgradeGroup("UnknownRace", 1, "DRIVE", 0, 0),
// e.GenericErrorText(e.ErrInputUnknownRace))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 555, "DRIVE", 0, 0),
// e.GenericErrorText(e.ErrInputEntityNotExists))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 2, "DRIVE", 0, 0),
// e.GenericErrorText(e.ErrShipsBusy))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 3, "DRIVE", 0, 0),
// e.GenericErrorText(e.ErrInputEntityNotOwned))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 1, "GUN", 0, 0),
// e.GenericErrorText(e.ErrInputTechUnknown))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 1, "CARGO", 0, 0),
// e.GenericErrorText(e.ErrInputUpgradeShipTechNotUsed))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 1, "ALL", 0, 2.0),
// e.GenericErrorText(e.ErrInputUpgradeParameterNotAllowed))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 2.0),
// e.GenericErrorText(e.ErrInputUpgradeTechLevelInsufficient))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 1.1),
// e.GenericErrorText(e.ErrInputUpgradeShipsAlreadyUpToDate))
// g.Race[Race_0_idx].SetTechLevel(game.TechDrive, 10.0)
// assert.Equal(t, 10.0, g.Race[Race_0_idx].TechLevel(game.TechDrive))
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 0, 10.0),
// e.GenericErrorText(e.ErrUpgradeInsufficientResources))
// assert.NoError(t, g.UpgradeGroup(Race_0.Name, 1, "DRIVE", 2, 1.2))
// assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 4)
// assert.Equal(t, uint(8), g.ShipGroups[0].Number)
// assert.Equal(t, uint(2), g.ShipGroups[3].Number)
// assert.Equal(t, game.StateInOrbit, g.ShipGroups[0].State())
// assert.Equal(t, game.StateUpgrade, g.ShipGroups[3].State())
// assert.ErrorContains(t,
// g.UpgradeGroup(Race_0.Name, 4, "DRIVE", 1, 1.3),
// e.GenericErrorText(e.ErrInputUpgradeGroupBreakNotAllowed))
// }
-4
View File
@@ -5,7 +5,3 @@ type Map struct {
Height uint32 `json:"height"`
Planet []Planet `json:"planets"`
}
func Destination(x1, y1, x2, y2 float64) float64 {
return 0
}
-52
View File
@@ -2,10 +2,8 @@ package game
import (
"math"
"slices"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
)
type UnidentifiedPlanet struct {
@@ -48,21 +46,6 @@ func (p Planet) ProductionCapacity() float64 {
return p.Industry*0.75 + p.Population*0.25
}
// Свободный производственный потенциал (L)
// промышленность * 0.75 + население * 0.25
// за вычетом затрат, расходуемых в течение хода на модернизацию кораблей
func PlanetProductionCapacity(g *Game, planetNumber uint) float64 {
p, err := g.PlanetByNumber(planetNumber)
if err != nil {
panic(err)
}
var busyResources float64
for sg := range g.ShipsInUpgrade(p.Number) {
busyResources += sg.StateUpgrade.Cost()
}
return PlanetProduction(p.Industry, p.Population) - busyResources
}
func PlanetProduction(industry, population float64) float64 {
return industry*0.75 + population*0.25
}
@@ -102,38 +85,3 @@ func UnloadColonists(p Planet, v float64) Planet {
}
return p
}
func (g Game) RenamePlanet(raceName string, planetNumber int, typeName string) error {
ri, err := g.raceIndex(raceName)
if err != nil {
return err
}
return g.renamePlanetInternal(ri, planetNumber, typeName)
}
func (g Game) renamePlanetInternal(ri int, number int, name string) error {
n, ok := validateTypeName(name)
if !ok {
return e.NewEntityTypeNameValidationError("%q", n)
}
if number < 0 {
return e.NewPlanetNumberError(number)
}
pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(number) })
if pl < 0 {
return e.NewEntityNotExistsError("planet #%d", number)
}
if g.Map.Planet[pl].Owner != g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet #%d", number)
}
g.Map.Planet[pl].Name = n
return nil
}
func PlanetByNum(g *Game, number uint) (Planet, bool) {
pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == number })
if pi < 0 {
return Planet{}, false
}
return g.Map.Planet[pi], true
}
-8
View File
@@ -12,11 +12,3 @@ func TestPlanetProduction(t *testing.T) {
assert.Equal(t, 750., game.PlanetProduction(1000., 0.))
assert.Equal(t, 250., game.PlanetProduction(0., 1000.))
}
func TestPlanetProductionCapacity(t *testing.T) {
g := newGame()
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
assert.Equal(t, 100., game.PlanetProductionCapacity(g, R0_Planet_0_num))
g.ShipGroups[0] = game.UpgradeGroupPreference(g.ShipGroups[0], Cruiser, game.TechDrive, 1.6)
assert.Equal(t, 53.125, game.PlanetProductionCapacity(g, R0_Planet_0_num))
}
-92
View File
@@ -1,10 +1,7 @@
package game
import (
"slices"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
)
type ProductionType string
@@ -37,92 +34,3 @@ func (p ProductionType) AsType(subject uuid.UUID) Production {
return Production{Type: p, SubjectID: nil}
}
}
func (g Game) PlanetProduction(raceName string, planetNumber int, prodType, subject string) error {
ri, err := g.raceIndex(raceName)
if err != nil {
return err
}
var prod ProductionType
switch ProductionType(prodType) {
case ProductionMaterial:
prod = ProductionMaterial
case ProductionCapital:
prod = ProductionCapital
case ResearchDrive:
prod = ResearchDrive
case ResearchWeapons:
prod = ResearchWeapons
case ResearchShields:
prod = ResearchShields
case ResearchCargo:
prod = ResearchCargo
case ResearchScience:
prod = ResearchScience
case ProductionShip:
prod = ProductionShip
default:
return e.NewProductionInvalidError(prodType)
}
return g.planetProductionInternal(ri, planetNumber, prod, subject)
}
func (g Game) planetProductionInternal(ri int, number int, prod ProductionType, subj string) error {
if number < 0 {
return e.NewPlanetNumberError(number)
}
i := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(number) })
if i < 0 {
return e.NewEntityNotExistsError("planet #%d", number)
}
if g.Map.Planet[i].Owner != g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet #%d", number)
}
g.Map.Planet[i].Production.Progress = nil
var subjectID *uuid.UUID
if (prod == ResearchScience || prod == ProductionShip) && subj == "" {
return e.NewEntityTypeNameValidationError("%s=%q", prod, subj)
}
if prod == ResearchScience {
i := slices.IndexFunc(g.Race[ri].Sciences, func(s Science) bool { return s.Name == subj })
if i < 0 {
return e.NewEntityNotExistsError("science %w", subj)
}
subjectID = &g.Race[ri].Sciences[i].ID
}
if prod == ProductionShip {
i := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == subj })
if i < 0 {
return e.NewEntityNotExistsError("ship type %w", subj)
}
if g.Map.Planet[i].Production.Type == ProductionShip &&
g.Map.Planet[i].Production.SubjectID != nil &&
*g.Map.Planet[i].Production.SubjectID == g.Race[ri].ShipTypes[i].ID {
// Planet already produces this ship type, keeping progress intact
return nil
}
subjectID = &g.Race[ri].ShipTypes[i].ID
var progress float64 = 0.
g.Map.Planet[i].Production.Progress = &progress
}
if g.Map.Planet[i].Production.Type == ProductionShip {
if g.Map.Planet[i].Production.SubjectID == nil {
return e.NewGameStateError("planet #%d produces ship but SubjectID is empty", g.Map.Planet[i].Number)
}
s := *g.Map.Planet[i].Production.SubjectID
if g.Map.Planet[i].Production.Progress == nil {
return e.NewGameStateError("planet #%d produces ship but Progress is empty", g.Map.Planet[i].Number)
}
progress := *g.Map.Planet[i].Production.Progress
i := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == s })
if i < 0 {
return e.NewGameStateError("planet #%d produces ship but ShipType was not found for race %s", g.Map.Planet[i].Number, g.Race[ri].Name)
}
mat, _ := g.Race[ri].ShipTypes[i].ProductionCost()
extra := mat * progress
g.Map.Planet[i].Material += extra
}
g.Map.Planet[i].Production.Type = prod
g.Map.Planet[i].Production.SubjectID = subjectID
return nil
}
+1 -83
View File
@@ -1,11 +1,6 @@
package game
import (
// "slices"
"github.com/google/uuid"
// e "github.com/iliadenisov/galaxy/internal/error"
)
import "github.com/google/uuid"
type Race struct {
ID uuid.UUID `json:"id"`
@@ -38,11 +33,6 @@ func (r Race) TechLevel(t Tech) float64 {
return r.Tech.Value(t)
}
// TODO: remove func, move to Cache
func (r *Race) SetTechLevel(t Tech, v float64) {
r.Tech = r.Tech.Set(t, v)
}
func (r Race) FlightDistance() float64 {
return r.TechLevel(TechDrive) * 40
}
@@ -50,75 +40,3 @@ func (r Race) FlightDistance() float64 {
func (r Race) VisibilityDistance() float64 {
return r.TechLevel(TechDrive) * 30
}
// func (g Game) Relation(hostRace, opponentRace string) (RaceRelation, error) {
// ri, err := g.raceIndex(hostRace)
// if err != nil {
// return RaceRelation{}, err
// }
// other, err := g.raceIndex(opponentRace)
// if err != nil {
// return RaceRelation{}, err
// }
// return g.relationInternal(ri, other)
// }
// func (g Game) UpdateRelation(race, opponent string, rel Relation) error {
// ri, err := g.raceIndex(race)
// if err != nil {
// return err
// }
// var other int
// if race == opponent {
// other = ri
// } else if other, err = g.raceIndex(opponent); err != nil {
// return err
// }
// if err != nil {
// return err
// }
// return g.updateRelationInternal(ri, other, rel)
// }
// func (g *Game) GiveVotes(race, recipient string) error {
// ri, err := g.raceIndex(race)
// if err != nil {
// return err
// }
// rec, err := g.raceIndex(recipient)
// if err != nil {
// return err
// }
// g.Race[ri].Vote = g.Race[rec].ID
// return nil
// }
// func (g Game) relationInternal(ri, other int) (RaceRelation, error) {
// if ri == other {
// return RaceRelation{
// RaceID: g.Race[ri].ID,
// Relation: RelationPeace,
// }, nil
// }
// rel := slices.IndexFunc(g.Race[ri].Relations, func(r RaceRelation) bool { return r.RaceID == g.Race[other].ID })
// if rel < 0 {
// return RaceRelation{}, e.NewGameStateError("Relation: opponent not found")
// }
// return g.Race[ri].Relations[rel], nil
// }
// func (g Game) updateRelationInternal(ri, other int, rel Relation) error {
// for o := range g.Race[ri].Relations {
// switch {
// case ri == other:
// g.Race[ri].Relations[o].Relation = rel
// case g.Race[ri].Relations[o].RaceID == g.Race[other].ID:
// g.Race[ri].Relations[o].Relation = rel
// return nil
// }
// }
// if ri != other {
// return e.NewGameStateError("UpdateRelation: opponent not found")
// }
// return nil
// }
+34
View File
@@ -1 +1,35 @@
package game_test
import (
"testing"
"github.com/iliadenisov/galaxy/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())
}
+1 -90
View File
@@ -1,13 +1,5 @@
package game
import (
"fmt"
"slices"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/iliadenisov/galaxy/internal/util"
)
type RouteType string
const (
@@ -18,7 +10,7 @@ const (
)
var (
routeTypeSet map[string]RouteType = map[string]RouteType{
RouteTypeSet map[string]RouteType = map[string]RouteType{
RouteMaterial.String(): RouteMaterial,
RouteCapital.String(): RouteCapital,
RouteColonist.String(): RouteColonist,
@@ -33,84 +25,3 @@ func (rt RouteType) Ref() *RouteType {
func (rt RouteType) String() string {
return string(rt)
}
func (g *Game) SetRoute(raceName, loadType string, origin, destination uint) error {
ri, err := g.raceIndex(raceName)
if err != nil {
return err
}
rt, ok := routeTypeSet[loadType]
if !ok {
return e.NewCargoTypeInvalidError(loadType)
}
return g.setRouteInternal(ri, rt, origin, destination)
}
func (g *Game) RemoveRoute(raceName, loadType string, origin uint) error {
ri, err := g.raceIndex(raceName)
if err != nil {
return err
}
rt, ok := routeTypeSet[loadType]
if !ok {
return e.NewCargoTypeInvalidError(loadType)
}
return g.removeRouteInternal(ri, rt, origin)
}
func (g *Game) setRouteInternal(ri int, rt RouteType, origin, destination uint) error {
p1, ok := PlanetByNum(g, origin)
if !ok {
return e.NewEntityNotExistsError("origin planet #%d", origin)
}
if p1.Owner != g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet #%d", origin)
}
p2, ok := PlanetByNum(g, destination)
if !ok {
return e.NewEntityNotExistsError("destination planet #%d", destination)
}
rangeToDestination := util.ShortDistance(g.Map.Width, g.Map.Height, p1.X, p1.Y, p2.X, p2.Y)
if rangeToDestination > g.Race[ri].FlightDistance() {
return e.NewSendUnreachableDestinationError("range=%.03f", rangeToDestination)
}
SetPlanetRoute(g, rt, origin, destination)
return nil
}
func (g *Game) removeRouteInternal(ri int, rt RouteType, origin uint) error {
p1, ok := PlanetByNum(g, origin)
if !ok {
return e.NewEntityNotExistsError("origin planet #%d", origin)
}
if p1.Owner != g.Race[ri].ID {
return e.NewEntityNotOwnedError("planet #%d", origin)
}
RemovePlanetRoute(g, rt, origin)
return nil
}
func SetPlanetRoute(g *Game, rt RouteType, origin, destination uint) {
pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == origin })
if pi < 0 {
panic(fmt.Sprintf("SetPlanetRoute: origin planet #%d not found", origin))
}
if g.Map.Planet[pi].Route == nil {
g.Map.Planet[pi].Route = make(map[RouteType]uint)
}
g.Map.Planet[pi].Route[rt] = destination
}
func RemovePlanetRoute(g *Game, rt RouteType, origin uint) {
pi := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == origin })
if pi < 0 {
panic(fmt.Sprintf("RemovePlanetRoute: origin planet #%d not found", origin))
}
if g.Map.Planet[pi].Route != nil {
delete(g.Map.Planet[pi].Route, rt)
}
}
-91
View File
@@ -1,91 +0,0 @@
package game_test
import (
"testing"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/iliadenisov/galaxy/internal/model/game"
"github.com/stretchr/testify/assert"
)
func TestSetRoute(t *testing.T) {
g := newGame()
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial)
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital)
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist)
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty)
assert.NoError(t, g.SetRoute(Race_0.Name, "COL", 0, 2))
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial)
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist)
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty)
assert.NoError(t, g.SetRoute(Race_0.Name, "MAT", 0, 2))
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial)
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist)
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty)
assert.NoError(t, g.SetRoute(Race_0.Name, "CAP", 0, 2))
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist)
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty)
assert.NoError(t, g.SetRoute(Race_0.Name, "EMP", 0, 2))
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteMaterial)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteEmpty)
assert.ErrorContains(t,
g.SetRoute("UnknownRace", "COL", 0, 2),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.SetRoute(Race_0.Name, "IND", 0, 2),
e.GenericErrorText(e.ErrInputCargoTypeInvalid))
assert.ErrorContains(t,
g.SetRoute(Race_0.Name, "COL", 500, 2),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.ErrorContains(t,
g.SetRoute(Race_0.Name, "COL", 1, 2),
e.GenericErrorText(e.ErrInputEntityNotOwned))
assert.ErrorContains(t,
g.SetRoute(Race_0.Name, "COL", 0, 3),
e.GenericErrorText(e.ErrSendUnreachableDestination))
}
func TestRemoveRoute(t *testing.T) {
g := newGame()
assert.NoError(t, g.SetRoute(Race_0.Name, "COL", 0, 2))
assert.NoError(t, g.SetRoute(Race_0.Name, "CAP", 0, 2))
assert.NoError(t, g.SetRoute(Race_0.Name, "EMP", 2, 0))
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital)
assert.Contains(t, g.MustPlanetByNumber(2).Route, game.RouteEmpty)
assert.NoError(t, g.RemoveRoute(Race_0.Name, "COL", 0))
assert.NotContains(t, g.MustPlanetByNumber(0).Route, game.RouteColonist)
assert.Contains(t, g.MustPlanetByNumber(0).Route, game.RouteCapital)
assert.NoError(t, g.RemoveRoute(Race_0.Name, "EMP", 2))
assert.NotContains(t, g.MustPlanetByNumber(2).Route, game.RouteEmpty)
assert.ErrorContains(t,
g.RemoveRoute("UnknownRace", "COL", 0),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.RemoveRoute(Race_0.Name, "IND", 0),
e.GenericErrorText(e.ErrInputCargoTypeInvalid))
assert.ErrorContains(t,
g.RemoveRoute(Race_0.Name, "COL", 500),
e.GenericErrorText(e.ErrInputEntityNotExists))
assert.ErrorContains(t,
g.RemoveRoute(Race_0.Name, "COL", 1),
e.GenericErrorText(e.ErrInputEntityNotOwned))
}
-84
View File
@@ -1,10 +1,7 @@
package game
import (
"slices"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
)
type Science struct {
@@ -24,84 +21,3 @@ type ScienceReport struct {
Shields float64 `json:"shields"`
Cargo float64 `json:"cargo"`
}
func (g Game) Sciences(raceName string) ([]Science, error) {
ri, err := g.raceIndex(raceName)
if err != nil {
return nil, err
}
return g.sciencesInternal(ri), nil
}
func (g Game) sciencesInternal(ri int) []Science {
return g.Race[ri].Sciences
}
func (g Game) DeleteScience(raceName, typeName string) error {
ri, err := g.raceIndex(raceName)
if err != nil {
return err
}
return g.deleteScienceInternal(ri, typeName)
}
func (g Game) deleteScienceInternal(ri int, name string) error {
sc := slices.IndexFunc(g.Race[ri].Sciences, func(s Science) bool { return s.Name == name })
if sc < 0 {
return e.NewEntityNotExistsError("science %w", name)
}
if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool {
return p.Production.Type == ResearchScience &&
p.Production.SubjectID != nil &&
*p.Production.SubjectID == g.Race[ri].Sciences[sc].ID
}); pl >= 0 {
return e.NewDeleteSciencePlanetProductionError(g.Map.Planet[pl].Name)
}
g.Race[ri].Sciences = append(g.Race[ri].Sciences[:sc], g.Race[ri].Sciences[sc+1:]...)
return nil
}
func (g Game) CreateScience(raceName, typeName string, d, w, s, c float64) error {
ri, err := g.raceIndex(raceName)
if err != nil {
return err
}
return g.createScienceInternal(ri, typeName, d, w, s, c)
}
func (g Game) createScienceInternal(ri int, name string, d, w, s, c float64) error {
n, ok := validateTypeName(name)
if !ok {
return e.NewEntityTypeNameValidationError("%q", n)
}
if sc := slices.IndexFunc(g.Race[ri].Sciences, func(s Science) bool { return s.Name == n }); sc >= 0 {
return e.NewEntityTypeNameDuplicateError("science %w", g.Race[ri].Sciences[sc].Name)
}
if d < 0 {
return e.NewDriveValueError(d)
}
if w < 0 {
return e.NewWeaponsValueError(w)
}
if s < 0 {
return e.NewShieldsValueError(s)
}
if c < 0 {
return e.NewCargoValueError(c)
}
sum := d + w + s + c
if sum != 1 {
return e.NewScienceSumValuesError("D=%f W=%f S=%f C=%f sum=%f", d, w, s, c, sum)
}
g.Race[ri].Sciences = append(g.Race[ri].Sciences, Science{
ID: uuid.New(),
ScienceReport: ScienceReport{
Name: n,
Drive: d,
Weapons: w,
Shields: s,
Cargo: c,
},
})
return nil
}
-184
View File
@@ -1,11 +1,7 @@
package game
import (
"fmt"
"slices"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
)
type ShipTypeReport struct {
@@ -80,183 +76,3 @@ func (st ShipType) ProductionCost() (mat float64, pop float64) {
pop = mat * 10
return
}
func (g Game) mustShipType(id uuid.UUID) *ShipType {
for ri := range g.Race {
if st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == id }); st >= 0 {
return &g.Race[ri].ShipTypes[st]
}
}
panic(fmt.Sprintf("mustShipType: ShipType not found: %v", id))
}
func (g Game) ShipTypes(raceName string) ([]ShipType, error) {
ri, err := g.raceIndex(raceName)
if err != nil {
return nil, err
}
return g.shipTypesInternal(ri), nil
}
func (g Game) shipTypesInternal(ri int) []ShipType {
return g.Race[ri].ShipTypes
}
// func (g Game) DeleteShipType(raceName, typeName string) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// return g.deleteShipTypeInternal(ri, typeName)
// }
// func (g Game) deleteShipTypeInternal(ri int, name string) error {
// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name })
// if st < 0 {
// return e.NewEntityNotExistsError("ship type %w", name)
// }
// if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool {
// return p.Production.Type == ProductionShip &&
// p.Production.SubjectID != nil &&
// g.Race[ri].ShipTypes[st].ID == *p.Production.SubjectID
// }); pl >= 0 {
// return e.NewDeleteShipTypePlanetProductionError(g.Map.Planet[pl].Name)
// }
// for sg := range g.listShipGroups(ri) {
// if sg.TypeID == g.Race[ri].ShipTypes[st].ID {
// return e.NewDeleteShipTypeExistingGroupError("group: %v", sg.Index)
// }
// }
// g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...)
// return nil
// }
// TODO: D A W S C
// func (g Game) CreateShipType(raceName, typeName string, d, w, s, c float64, a int) error {
// ri, err := g.raceIndex(raceName)
// if err != nil {
// return err
// }
// _, err = g.createShipTypeInternal(ri, typeName, d, w, s, c, a)
// return err
// }
// func (g Game) createShipTypeInternal(ri int, name string, d, w, s, c float64, a int) (int, error) {
// if err := checkShipTypeValues(d, w, s, c, a); err != nil {
// return -1, err
// }
// n, ok := validateTypeName(name)
// if !ok {
// return -1, e.NewEntityTypeNameValidationError("%q", n)
// }
// if st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name }); st >= 0 {
// return -1, e.NewEntityTypeNameDuplicateError("ship type %w", g.Race[ri].ShipTypes[st].Name)
// }
// g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes, ShipType{
// ID: uuid.New(),
// ShipTypeReport: ShipTypeReport{
// Name: n,
// Drive: d,
// Weapons: w,
// Shields: s,
// Cargo: c,
// Armament: uint(a),
// },
// })
// return len(g.Race[ri].ShipTypes) - 1, nil
// }
// func (g Game) MergeShipType(race, name, targetName string) error {
// ri, err := g.raceIndex(race)
// if err != nil {
// return err
// }
// return g.mergeShipTypeInternal(ri, name, targetName)
// }
// func (g Game) mergeShipTypeInternal(ri int, name, targetName string) error {
// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name })
// if st < 0 {
// return e.NewEntityNotExistsError("source ship type %w", name)
// }
// if name == targetName {
// return e.NewEntityTypeNameEqualityError("ship type %q", targetName)
// }
// tt := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == targetName })
// if tt < 0 {
// return e.NewEntityNotExistsError("target ship type %w", name)
// }
// if !g.Race[ri].ShipTypes[st].Equal(g.Race[ri].ShipTypes[tt]) {
// return e.NewMergeShipTypeNotEqualError()
// }
// // switch planet productions to the new type
// for pl := range g.Map.Planet {
// if g.Map.Planet[pl].Owner == g.Race[ri].ID &&
// g.Map.Planet[pl].Production.Type == ProductionShip &&
// g.Map.Planet[pl].Production.SubjectID != nil &&
// *g.Map.Planet[pl].Production.SubjectID == g.Race[ri].ShipTypes[st].ID {
// g.Map.Planet[pl].Production.SubjectID = &g.Race[ri].ShipTypes[tt].ID
// }
// }
// // switch ship groups to the new type
// for sg := range g.ShipGroups {
// if g.ShipGroups[sg].OwnerID == g.Race[ri].ID && g.ShipGroups[sg].TypeID == g.Race[ri].ShipTypes[st].ID {
// g.ShipGroups[sg].TypeID = g.Race[ri].ShipTypes[tt].ID
// }
// }
// // remove the source type
// g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...)
// return nil
// }
func checkShipTypeValues(d, w, s, c float64, a int) error {
if !checkShipTypeValueDWSC(d) {
return e.NewDriveValueError(d)
}
if !checkShipTypeValueDWSC(w) {
return e.NewWeaponsValueError(w)
}
if !checkShipTypeValueDWSC(s) {
return e.NewShieldsValueError(s)
}
if !checkShipTypeValueDWSC(c) {
return e.NewCargoValueError(s)
}
if a < 0 {
return e.NewShipTypeArmamentValueError(a)
}
if (w == 0 && a > 0) || (a == 0 && w > 0) {
return e.NewShipTypeArmamentAndWeaponsValueError("A=%d W=%.0f", a, w)
}
if d == 0 && w == 0 && s == 0 && c == 0 && a == 0 {
return e.NewShipTypeShipTypeZeroValuesError()
}
return nil
}
func checkShipTypeValueDWSC(v float64) bool {
return v == 0 || v >= 1
}
func ShipClass(g *Game, ri int, classID uuid.UUID) (ShipType, bool) {
if len(g.Race) < ri+1 {
panic(fmt.Sprintf("ShipClass: game race index %d invalid: len=%d", ri, len(g.Race)))
}
sti := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == classID })
if sti < 0 {
return ShipType{}, false
}
return g.Race[ri].ShipTypes[sti], true
}
func ShipClassIndex(g *Game, ri int, classID uuid.UUID) (int, bool) {
if len(g.Race) < ri+1 {
panic(fmt.Sprintf("ShipClass: game race index %d invalid: len=%d", ri, len(g.Race)))
}
sti := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == classID })
return sti, sti >= 0
}