package game import ( "iter" "math" "slices" "github.com/google/uuid" e "github.com/iliadenisov/galaxy/pkg/error" "github.com/iliadenisov/galaxy/pkg/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(g.listShipGroups(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.listShipGroups(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.ShipGroups { if g.ShipGroups[sg].OwnerID == g.Race[ri].ID { if !yield(g.ShipGroups[sg]) { return } } } } } func maxUint(a, b uint) uint { if b > a { return b } return a }