wip: battle mechanism
This commit is contained in:
+195
-75
@@ -2,15 +2,20 @@ package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Battle struct {
|
||||
Planet uint
|
||||
Groups []int // ShipGroup indexes
|
||||
BattleReport BattleReport
|
||||
|
||||
shipAmmo map[int]uint
|
||||
attacker map[int]map[int]float64 // a group able to attack and destroy an opponent with some probability
|
||||
}
|
||||
|
||||
type BattleReport struct {
|
||||
@@ -23,100 +28,215 @@ type BattleAction struct {
|
||||
Destroyed bool
|
||||
}
|
||||
|
||||
type BattleOpponent struct {
|
||||
RaceIndex int
|
||||
ShipGroupIndex int
|
||||
ShipType ShipType
|
||||
}
|
||||
func CollectPlanetGroups(g *Game, cacheShipGroupRaceID map[int]int, cacheShipClass map[int]*ShipType) map[uint][]int {
|
||||
planetGroup := make(map[uint][]int)
|
||||
for groupIndex := range g.ShipGroups {
|
||||
if g.ShipGroups[groupIndex].State() == StateInOrbit {
|
||||
planetNumber := g.ShipGroups[groupIndex].Destination
|
||||
planetGroup[planetNumber] = append(planetGroup[planetNumber], groupIndex)
|
||||
|
||||
func ProduceBattles(g *Game) error {
|
||||
|
||||
battleOnPlanet := Battle{}
|
||||
_ = battleOnPlanet
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SingleBattle(g *Game, b Battle) {
|
||||
attacker := SelectAttackShip(g, b.Groups)
|
||||
for shots := range attacker.ShipType.Armament {
|
||||
// groupsCopy := slices.Clone(b.Groups)
|
||||
// groupsWithoutAttacker := append(groupsCopy[:attackerIdx], groupsCopy[attackerIdx+1:]...)
|
||||
_ = SelectDefendShip(g, slices.Clone(b.Groups), attacker)
|
||||
|
||||
_ = shots
|
||||
if _, ok := cacheShipGroupRaceID[groupIndex]; !ok {
|
||||
cacheShipGroupRaceID[groupIndex] = RaceIndex(g, g.ShipGroups[groupIndex].OwnerID)
|
||||
}
|
||||
}
|
||||
ri := cacheShipGroupRaceID[groupIndex]
|
||||
|
||||
func SelectAttackShip(g *Game, battleGroups []int) BattleOpponent {
|
||||
sgi := rand.IntN(len(battleGroups))
|
||||
if sgi > len(g.ShipGroups)-1 {
|
||||
panic("SelectAttackShip: battleGroups is bigger than game's ship groups")
|
||||
}
|
||||
sg := g.ShipGroups[sgi]
|
||||
ri := slices.IndexFunc(g.Race, func(r Race) bool { return r.ID == sg.OwnerID })
|
||||
if ri < 0 {
|
||||
panic(fmt.Sprintf("SelectAttackShip: ship group #%v owner race not found by ID=%v", sg.Index, sg.OwnerID))
|
||||
}
|
||||
st, ok := ShipClass(g, ri, sg.TypeID)
|
||||
if _, ok := cacheShipClass[groupIndex]; !ok {
|
||||
sti, ok := ShipClassIndex(g, ri, g.ShipGroups[groupIndex].TypeID)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("SelectAttackShip: ship class not found for race=%q group=%v", g.Race[ri].Name, sg.Index))
|
||||
panic(fmt.Sprintf("CollectPlanetGroups: ship class not found for race=%q group=%v", g.Race[ri].Name, g.ShipGroups[groupIndex].Index))
|
||||
}
|
||||
if st.Weapons == 0 || st.Armament == 0 {
|
||||
panic(fmt.Sprintf("SelectAttackShip: ship_class=%q of race=%q has no weapons for attack", st.Name, g.Race[ri].Name))
|
||||
cacheShipClass[groupIndex] = &g.Race[ri].ShipTypes[sti]
|
||||
}
|
||||
return BattleOpponent{
|
||||
RaceIndex: ri,
|
||||
ShipGroupIndex: sgi,
|
||||
ShipType: st,
|
||||
}
|
||||
}
|
||||
for pl := range planetGroup {
|
||||
if len(planetGroup[pl]) < 2 {
|
||||
delete(planetGroup, pl)
|
||||
}
|
||||
}
|
||||
return planetGroup
|
||||
}
|
||||
|
||||
func SelectDefendShip(g *Game, battleGroups []int, attacker BattleOpponent) BattleOpponent {
|
||||
enemyGroups := FilterAttackingPretendent(g, attacker.RaceIndex, battleGroups)
|
||||
sgi := rand.IntN(len(enemyGroups))
|
||||
if sgi > len(g.ShipGroups)-1 {
|
||||
panic("SelectDefendShip: battleGroups is bigger than game's ship groups")
|
||||
func CacheRelations(g *Game, cacheShipGroupRaceID map[int]int) map[int]map[int]Relation {
|
||||
cache := make(map[int]map[int]Relation)
|
||||
ri := make(map[int]bool)
|
||||
for _, raceIdx := range cacheShipGroupRaceID {
|
||||
ri[raceIdx] = true
|
||||
}
|
||||
sg := g.ShipGroups[sgi]
|
||||
ri := slices.IndexFunc(g.Race, func(r Race) bool { return r.ID == sg.OwnerID })
|
||||
if ri < 0 {
|
||||
panic(fmt.Sprintf("SelectDefendShip: ship group #%v owner race not found by ID=%v", sg.Index, sg.OwnerID))
|
||||
for r1 := range ri {
|
||||
for r2 := range ri {
|
||||
if r1 == r2 {
|
||||
continue
|
||||
}
|
||||
st, ok := ShipClass(g, ri, sg.TypeID)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("SelectDefendShip: ship class not found for race=%q group=%v", g.Race[ri].Name, sg.Index))
|
||||
}
|
||||
return BattleOpponent{
|
||||
RaceIndex: ri,
|
||||
ShipGroupIndex: sgi,
|
||||
ShipType: st,
|
||||
}
|
||||
}
|
||||
|
||||
// attackerIdx - attacker race index
|
||||
func FilterAttackingPretendent(g *Game, attackerIdx int, battleGroups []int) []int {
|
||||
result := make([]int, 0)
|
||||
for sgi := range battleGroups {
|
||||
sg := g.ShipGroups[sgi]
|
||||
enemyIdx := slices.IndexFunc(g.Race, func(r Race) bool { return r.ID == sg.OwnerID })
|
||||
if enemyIdx < 0 {
|
||||
panic(fmt.Sprintf("FilterAttackingPretendent: ship group #%v owner race not found by ID=%v", sg.Index, sg.OwnerID))
|
||||
}
|
||||
rel, err := g.relationInternal(attackerIdx, enemyIdx)
|
||||
rel, err := g.relationInternal(r1, r2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// attacker race will be in peace with itself, so attacker ships will be filtered out as well
|
||||
if rel.Relation == RelationPeace {
|
||||
if _, ok := cache[r1]; !ok {
|
||||
cache[r1] = make(map[int]Relation)
|
||||
}
|
||||
cache[r1][r2] = rel.Relation
|
||||
}
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
func FilterBattleOpponents(
|
||||
g *Game,
|
||||
attIdx, defIdx int,
|
||||
cacheShipGroupRaceID map[int]int,
|
||||
cacheRelation map[int]map[int]Relation,
|
||||
cacheShipClass map[int]*ShipType,
|
||||
cacheProbability map[int]map[int]float64,
|
||||
) bool {
|
||||
// Same Race's groups can't attack themselves
|
||||
if attIdx == defIdx || g.ShipGroups[attIdx].OwnerID == g.ShipGroups[defIdx].OwnerID {
|
||||
return true
|
||||
}
|
||||
|
||||
// If any opponent has War relation to another, both will stay in battle
|
||||
if cacheRelation[cacheShipGroupRaceID[attIdx]][cacheShipGroupRaceID[defIdx]] == RelationPeace &&
|
||||
cacheRelation[cacheShipGroupRaceID[defIdx]][cacheShipGroupRaceID[attIdx]] == RelationPeace {
|
||||
return true
|
||||
}
|
||||
|
||||
p := DestructionProbability(
|
||||
cacheShipClass[attIdx].Weapons,
|
||||
g.ShipGroups[attIdx].TechLevel(TechWeapons),
|
||||
cacheShipClass[defIdx].Shields,
|
||||
g.ShipGroups[defIdx].TechLevel(TechShields),
|
||||
g.ShipGroups[defIdx].FullMass(cacheShipClass[defIdx]),
|
||||
)
|
||||
// Exclude opponent's group which cannot be probably destroyed
|
||||
if p <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := cacheProbability[attIdx]; !ok {
|
||||
cacheProbability[attIdx] = make(map[int]float64)
|
||||
}
|
||||
cacheProbability[attIdx][defIdx] = p
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
func ProduceBattles(g *Game) []*Battle {
|
||||
cacheShipGroupRaceID := make(map[int]int)
|
||||
cacheShipClass := make(map[int]*ShipType)
|
||||
cacheProbability := make(map[int]map[int]float64)
|
||||
|
||||
defer func() {
|
||||
clear(cacheShipGroupRaceID)
|
||||
clear(cacheShipClass)
|
||||
clear(cacheProbability)
|
||||
}()
|
||||
|
||||
planetGroup := CollectPlanetGroups(g, cacheShipGroupRaceID, cacheShipClass)
|
||||
if len(planetGroup) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cacheRelation := CacheRelations(g, cacheShipGroupRaceID)
|
||||
defer func() {
|
||||
clear(cacheRelation)
|
||||
}()
|
||||
|
||||
result := make([]*Battle, 0)
|
||||
|
||||
for pl, groups := range planetGroup {
|
||||
b := &Battle{
|
||||
Planet: pl,
|
||||
attacker: make(map[int]map[int]float64),
|
||||
shipAmmo: make(map[int]uint),
|
||||
}
|
||||
|
||||
for i := range groups {
|
||||
attIdx := groups[i]
|
||||
|
||||
// Ships with no Ammo will never attack somebody
|
||||
if cacheShipClass[attIdx].Armament == 0 {
|
||||
continue
|
||||
}
|
||||
result = append(result, sgi)
|
||||
|
||||
// TODO: remove slices.Clone?
|
||||
opponents := slices.DeleteFunc(slices.Clone(groups), func(defIdx int) bool {
|
||||
return FilterBattleOpponents(g, attIdx, defIdx, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability)
|
||||
})
|
||||
if len(opponents) > 0 {
|
||||
b.shipAmmo[attIdx] = cacheShipClass[attIdx].Armament
|
||||
for _, defIdx := range opponents {
|
||||
b.attacker[attIdx][defIdx] = cacheProbability[attIdx][defIdx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(b.attacker) > 0 {
|
||||
SingleBattle(g, b)
|
||||
result = append(result, b)
|
||||
}
|
||||
|
||||
clear(b.attacker)
|
||||
clear(b.shipAmmo)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func DestroyProbability(attWeapons, attWeaponsTech, defShields, defShiledsTech, defFullMass float64) float64 {
|
||||
func SingleBattle(g *Game, b *Battle) {
|
||||
for len(b.attacker) > 0 {
|
||||
attackers := slices.Collect(maps.Keys(b.attacker))
|
||||
attIdx := attackers[rand.IntN(len(attackers))]
|
||||
|
||||
for range b.shipAmmo[attIdx] {
|
||||
defenders := slices.Collect(maps.Keys(b.attacker[attIdx]))
|
||||
defIdx := defenders[rand.IntN(len(defenders))]
|
||||
destroyed := false
|
||||
|
||||
probability := b.attacker[attIdx][defIdx]
|
||||
switch {
|
||||
case probability >= 1:
|
||||
destroyed = true
|
||||
case probability > 0:
|
||||
destroyed = rand.Float64() >= probability
|
||||
default:
|
||||
panic("probabilities cache returned unexpected value")
|
||||
}
|
||||
|
||||
b.BattleReport.BattleAction = append(b.BattleReport.BattleAction, BattleAction{
|
||||
Attacker: attIdx,
|
||||
Defenter: defIdx,
|
||||
Destroyed: destroyed,
|
||||
})
|
||||
|
||||
if destroyed {
|
||||
g.ShipGroups[defIdx].Number--
|
||||
}
|
||||
if g.ShipGroups[defIdx].Number == 0 {
|
||||
delete(b.attacker, defIdx) // Eliminated group cant attack anyone
|
||||
for attIdx := range b.attacker {
|
||||
delete(b.attacker[attIdx], defIdx) // Attackers can't attack eliminated group anymore
|
||||
if len(b.attacker[attIdx]) == 0 {
|
||||
delete(b.attacker, attIdx) // Remove attacker if he lost all opponents
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(b.attacker) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RaceIndex(g *Game, ID uuid.UUID) int {
|
||||
i := slices.IndexFunc(g.Race, func(r Race) bool { return r.ID == ID })
|
||||
if i < 0 {
|
||||
panic(fmt.Sprintf("RaceIndex: race not found by ID=%v", ID))
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func DestructionProbability(attWeapons, attWeaponsTech, defShields, defShiledsTech, defFullMass float64) float64 {
|
||||
effAttack := attWeapons * attWeaponsTech
|
||||
effDefence := EffectiveDefence(defShields, defShiledsTech, defFullMass)
|
||||
return (math.Log10(effAttack/effDefence)/math.Log10(4) + 1) / 2
|
||||
|
||||
@@ -40,18 +40,18 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func TestDestroyProbability(t *testing.T) {
|
||||
probability := game.DestroyProbability(ship.Weapons, 1, ship.Shields, 1, ship.EmptyMass())
|
||||
func TestDestructionProbability(t *testing.T) {
|
||||
probability := game.DestructionProbability(ship.Weapons, 1, ship.Shields, 1, ship.EmptyMass())
|
||||
assert.Equal(t, .5, probability)
|
||||
|
||||
unsinkableShip := ship
|
||||
unsinkableShip.Shields = 55
|
||||
probability = game.DestroyProbability(ship.Weapons, 1, unsinkableShip.Shields, 1, unsinkableShip.EmptyMass())
|
||||
undefeatedShip := ship
|
||||
undefeatedShip.Shields = 55
|
||||
probability = game.DestructionProbability(ship.Weapons, 1, undefeatedShip.Shields, 1, undefeatedShip.EmptyMass())
|
||||
assert.LessOrEqual(t, probability, 0.)
|
||||
|
||||
disruptiveShip := ship
|
||||
disruptiveShip.Weapons = 40
|
||||
probability = game.DestroyProbability(disruptiveShip.Weapons, 1, ship.Shields, 1, ship.EmptyMass())
|
||||
probability = game.DestructionProbability(disruptiveShip.Weapons, 1, ship.Shields, 1, ship.EmptyMass())
|
||||
assert.GreaterOrEqual(t, probability, 1.)
|
||||
}
|
||||
|
||||
@@ -64,3 +64,100 @@ func TestEffectiveDefence(t *testing.T) {
|
||||
// attacker's effective shields must be 'just' 4 times greater than defender's
|
||||
assert.InDelta(t, defenderEffectiveDefence*4, attackerEffectiveDefence, 0)
|
||||
}
|
||||
|
||||
func TestCollectPlanetGroups(t *testing.T) {
|
||||
g := newGame()
|
||||
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 10)) // 0
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 3)) // 1
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) // 2
|
||||
g.ShipGroups[2].StateInSpace = &game.InSpace{Origin: 2, Range: 1.23} // 2 -> In_Space
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1)) // 3
|
||||
g.ShipGroups[3].Destination = R1_Planet_1_num // 3 -> Planet_1
|
||||
assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 15)) // 4
|
||||
g.ShipGroups[4].Destination = R0_Planet_0_num // 4 -> Planet_0
|
||||
|
||||
cacheShipGroupRaceID := make(map[int]int)
|
||||
cacheShipClass := make(map[int]*game.ShipType)
|
||||
planetGroups := game.CollectPlanetGroups(g, cacheShipGroupRaceID, cacheShipClass)
|
||||
|
||||
for pl := range planetGroups {
|
||||
switch pl {
|
||||
case R0_Planet_0_num:
|
||||
assert.Equal(t, 3, len(planetGroups[pl]))
|
||||
assert.Contains(t, planetGroups[pl], 0)
|
||||
assert.Contains(t, planetGroups[pl], 1)
|
||||
assert.Contains(t, planetGroups[pl], 4)
|
||||
default:
|
||||
assert.Fail(t, "planet #%d should not contain groups for battle", pl)
|
||||
}
|
||||
}
|
||||
assert.Len(t, cacheShipGroupRaceID, 4)
|
||||
assert.Contains(t, cacheShipGroupRaceID, 0)
|
||||
assert.Contains(t, cacheShipGroupRaceID, 1)
|
||||
assert.Contains(t, cacheShipGroupRaceID, 3)
|
||||
assert.Contains(t, cacheShipGroupRaceID, 4)
|
||||
assert.Equal(t, Race_0_idx, cacheShipGroupRaceID[0])
|
||||
assert.Equal(t, Race_0_idx, cacheShipGroupRaceID[1])
|
||||
assert.Equal(t, Race_0_idx, cacheShipGroupRaceID[3])
|
||||
assert.Equal(t, Race_1_idx, cacheShipGroupRaceID[4])
|
||||
|
||||
assert.Len(t, cacheShipClass, 4) // all registered ship classes for all In_Orbit ship groups
|
||||
|
||||
cacheRelation := game.CacheRelations(g, cacheShipGroupRaceID)
|
||||
assert.Len(t, cacheRelation, 2)
|
||||
assert.Len(t, cacheRelation[Race_0_idx], 1)
|
||||
assert.Len(t, cacheRelation[Race_1_idx], 1)
|
||||
assert.Equal(t, game.RelationWar, cacheRelation[Race_0_idx][Race_1_idx])
|
||||
assert.Equal(t, game.RelationPeace, cacheRelation[Race_1_idx][Race_0_idx])
|
||||
assert.Empty(t, cacheRelation[Race_0_idx][Race_0_idx])
|
||||
assert.Empty(t, cacheRelation[Race_1_idx][Race_1_idx])
|
||||
}
|
||||
|
||||
func TestFilterBattleOpponents(t *testing.T) {
|
||||
g := newGame()
|
||||
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1)) // 0
|
||||
assert.NoError(t, g.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1)) // 1
|
||||
assert.NoError(t, g.CreateShips(Race_1_idx, Race_1_Gunship, R1_Planet_1_num, 15)) // 2
|
||||
undefeatedShip := ship
|
||||
undefeatedShip.Shields = 100
|
||||
assert.NoError(t, g.CreateShipType(Race_1.Name, undefeatedShip.Name, undefeatedShip.Drive, undefeatedShip.Weapons, undefeatedShip.Shields, undefeatedShip.Cargo, int(undefeatedShip.Armament)))
|
||||
assert.NoError(t, g.CreateShips(Race_1_idx, undefeatedShip.Name, R1_Planet_1_num, 1)) // 3
|
||||
|
||||
cacheShipGroupRaceID := make(map[int]int)
|
||||
cacheShipClass := make(map[int]*game.ShipType)
|
||||
cacheProbability := make(map[int]map[int]float64)
|
||||
cacheRelation := make(map[int]map[int]game.Relation)
|
||||
|
||||
game.CollectPlanetGroups(g, cacheShipGroupRaceID, cacheShipClass)
|
||||
|
||||
cacheRelation[Race_0_idx] = make(map[int]game.Relation)
|
||||
cacheRelation[Race_1_idx] = make(map[int]game.Relation)
|
||||
cacheRelation[Race_0_idx][Race_1_idx] = game.RelationPeace
|
||||
cacheRelation[Race_1_idx][Race_0_idx] = game.RelationWar
|
||||
|
||||
assert.False(t, game.FilterBattleOpponents(g, 0, 2, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability))
|
||||
assert.Contains(t, cacheProbability, 0)
|
||||
assert.Contains(t, cacheProbability[0], 2)
|
||||
assert.InDelta(t, 0.396222, cacheProbability[0][2], 0.0000001)
|
||||
assert.False(t, game.FilterBattleOpponents(g, 2, 0, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability))
|
||||
assert.Contains(t, cacheProbability, 2)
|
||||
assert.Contains(t, cacheProbability[2], 0)
|
||||
assert.InDelta(t, 0.495, cacheProbability[2][0], 0.0001)
|
||||
|
||||
// Test: same owner
|
||||
assert.True(t, game.FilterBattleOpponents(g, 0, 0, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability))
|
||||
assert.True(t, game.FilterBattleOpponents(g, 0, 1, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability))
|
||||
assert.True(t, game.FilterBattleOpponents(g, 1, 0, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability))
|
||||
|
||||
// Test: reace reations
|
||||
cacheRelation[Race_1_idx][Race_0_idx] = game.RelationPeace
|
||||
assert.True(t, game.FilterBattleOpponents(g, 0, 2, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability))
|
||||
assert.True(t, game.FilterBattleOpponents(g, 2, 0, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability))
|
||||
cacheRelation[Race_1_idx][Race_0_idx] = game.RelationWar
|
||||
|
||||
assert.LessOrEqual(t, game.DestructionProbability(Cruiser.Weapons, 1, undefeatedShip.Shields, 1, undefeatedShip.EmptyMass()), 0.)
|
||||
assert.True(t, game.FilterBattleOpponents(g, 1, 3, cacheShipGroupRaceID, cacheRelation, cacheShipClass, cacheProbability))
|
||||
assert.NotContains(t, cacheProbability[1], 3)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ var (
|
||||
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,
|
||||
@@ -30,6 +31,7 @@ var (
|
||||
game.TechShields: 2.3,
|
||||
game.TechCargo: 2.4,
|
||||
},
|
||||
Relations: []game.RaceRelation{{RaceID: Race_0_ID, Relation: game.RelationPeace}},
|
||||
}
|
||||
|
||||
Race_0_ID = uuid.New()
|
||||
|
||||
@@ -131,6 +131,7 @@ func (g Game) deleteShipTypeInternal(ri int, name string) error {
|
||||
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 {
|
||||
@@ -251,3 +252,11 @@ func ShipClass(g *Game, ri int, classID uuid.UUID) (ShipType, bool) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user