Files
galaxy-game/internal/model/game/group.go
T
2025-11-26 23:03:07 +03:00

207 lines
6.3 KiB
Go

package game
import (
"iter"
"maps"
"math"
"slices"
"github.com/google/uuid"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/iliadenisov/galaxy/internal/number"
)
type CargoType string
const (
// CargoNone CargoType = "-"
CargoColonist CargoType = "COL" // Колонисты
CargoMaterial CargoType = "MAT" // Сырьё
CargoCapital CargoType = "CAP" // Промышленность
)
type ShipGroup struct {
Index uint `json:"index"` // Group index (ordered)
OwnerID uuid.UUID `json:"ownerId"` // Race link
TypeID uuid.UUID `json:"typeId"` // ShipType link
FleetID *uuid.UUID `json:"fleetId,omitempty"` // ShipType link
Number uint `json:"number"` // Number (quantity) ships of Type
State string `json:"state"` // TODO: kinda enum: In_Orbit, In_Space, Transfer_State, Upgrade
CargoType *CargoType `json:"loadType,omitempty"`
Load float64 `json:"load"` // Cargo loaded - "Масса груза"
Drive float64 `json:"drive"`
Weapons float64 `json:"weapons"`
Shields float64 `json:"shields"`
Cargo float64 `json:"cargo"`
// TODO: append AND TEST: Destination, Origin, Range
Destination uint `json:"destination"`
}
func (sg ShipGroup) Equal(other ShipGroup) bool {
return sg.OwnerID == other.OwnerID &&
sg.TypeID == other.TypeID &&
sg.FleetID == other.FleetID &&
sg.Drive == other.Drive &&
sg.Weapons == other.Weapons &&
sg.Shields == other.Shields &&
sg.Cargo == other.Cargo &&
sg.CargoType == other.CargoType &&
sg.Load == other.Load &&
sg.State == other.State
}
// Грузоподъёмность
func (sg ShipGroup) CargoCapacity(st *ShipType) float64 {
return sg.Cargo * (st.Cargo + (st.Cargo*st.Cargo)/20) * float64(sg.Number)
}
// Масса перевозимого груза -
// общее количество единиц груза, деленное на технологический уровень Грузоперевозок
func (sg ShipGroup) CarryingMass() float64 {
return sg.Load / sg.Cargo
}
// Полная масса -
// массу корабля самого по себе плюс масса перевозимого груза.
func (sg ShipGroup) FullMass(st *ShipType) float64 {
return st.EmptyMass() + sg.CarryingMass()
}
// Эффективность двигателя -
// равна мощности Двигателей, умноженной на технологический уровень блока Двигателей
func (sg ShipGroup) DriveEffective(st *ShipType) float64 {
return st.Drive * sg.Drive
}
// Корабли перемещаются за один ход на количество световых лет, равное
// эффективности двигателя, умноженной на 20 и деленной на "Полную массу" корабля.
func (sg ShipGroup) Speed(st *ShipType) float64 {
return sg.DriveEffective(st) * 20 / sg.FullMass(st)
}
func (sg ShipGroup) UpgradeDriveCost(st *ShipType, drive float64) float64 {
return (1 - sg.Drive/drive) * 10 * st.Drive
}
// TODO: test on other values
func (sg ShipGroup) UpgradeWeaponsCost(st *ShipType, weapons float64) float64 {
return (1 - sg.Weapons/weapons) * 10 * st.WeaponsMass()
}
func (sg ShipGroup) UpgradeShieldsCost(st *ShipType, shields float64) float64 {
return (1 - sg.Shields/shields) * 10 * st.Shields
}
func (sg ShipGroup) UpgradeCargoCost(st *ShipType, cargo float64) float64 {
return (1 - sg.Cargo/cargo) * 10 * st.Cargo
}
// Мощность бомбардировки
// TODO: maybe rounding must be done only for display?
func (sg ShipGroup) BombingPower(st *ShipType) float64 {
// return math.Sqrt(sg.Type.Weapons * sg.Weapons)
result := (math.Sqrt(st.Weapons*sg.Weapons)/10. + 1.) *
st.Weapons *
sg.Weapons *
float64(st.Armament) *
float64(sg.Number)
return number.Fixed3(result)
}
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) 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),
State: "In_Orbit",
Drive: g.Race[ri].Drive,
Weapons: g.Race[ri].Weapons,
Shields: g.Race[ri].Shields,
Cargo: g.Race[ri].Cargo,
})
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 maxUint(a, b uint) uint {
if b > a {
return b
}
return a
}