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
-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
// }