3b1c52cd02
TurnWipeExtinctRaces iterated only non-extinct races, so an administratively banished race (flagged extinct, TTL untouched) was never wiped: its planets stayed owned and its ships lingered, while the race itself could no longer act. The loop now covers every race and wipes when either an active race's TTL has run out (idle / quit) or an extinct race still holds assets (banish). The asset check makes repeated passes idempotent. wipeRace already matched the rules for exclusion (ships removed, planets uninhabited, industry and capital cleared, material retained), so the behaviour is just documented in game/README.md. Tests: banish releases planets and ships on the next turn (and is idempotent); idle-timeout wipe still fires under the new iterator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
package controller_test
|
|
|
|
import (
|
|
"slices"
|
|
"testing"
|
|
|
|
e "galaxy/error"
|
|
|
|
"galaxy/game/internal/model/game"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestRaceVote(t *testing.T) {
|
|
c, g := newCache()
|
|
|
|
assert.Equal(t, c.Voted(Race_0_idx), Race_0_idx)
|
|
assert.Equal(t, c.Voted(Race_1_idx), Race_1_idx)
|
|
|
|
assert.NoError(t, g.RaceVote(Race_0.Name, Race_1.Name))
|
|
assert.Equal(t, Race_1_idx, c.Voted(Race_0_idx))
|
|
assert.Equal(t, Race_1_idx, c.Voted(Race_1_idx))
|
|
|
|
assert.ErrorContains(t,
|
|
g.RaceVote(UnknownRace, Race_1.Name),
|
|
e.GenericErrorText(e.ErrInputUnknownRace))
|
|
assert.ErrorContains(t,
|
|
g.RaceVote(Race_0.Name, UnknownRace),
|
|
e.GenericErrorText(e.ErrInputUnknownRace))
|
|
assert.ErrorContains(t,
|
|
g.RaceVote(Race_0.Name, Race_Extinct.Name),
|
|
e.GenericErrorText(e.ErrRaceExtinct))
|
|
assert.ErrorContains(t,
|
|
g.RaceVote(Race_Extinct.Name, Race_1.Name),
|
|
e.GenericErrorText(e.ErrRaceExtinct))
|
|
}
|
|
|
|
func TestRaceRelation(t *testing.T) {
|
|
c, g := newCache()
|
|
|
|
assert.NoError(t, g.RaceRelation(Race_0.Name, Race_1.Name, "war"))
|
|
assert.NoError(t, g.RaceRelation(Race_1.Name, Race_0.Name, "PEACE"))
|
|
|
|
assert.Equal(t, game.RelationWar, c.Relation(Race_0_idx, Race_1_idx))
|
|
assert.Equal(t, game.RelationPeace, c.Relation(Race_1_idx, Race_0_idx))
|
|
|
|
assert.ErrorContains(t,
|
|
g.RaceRelation(Race_0.Name, Race_1.Name, "Wojna"),
|
|
e.GenericErrorText(e.ErrInputUnknownRelation))
|
|
assert.ErrorContains(t,
|
|
g.RaceRelation(Race_0.Name, UnknownRace, "War"),
|
|
e.GenericErrorText(e.ErrInputUnknownRace))
|
|
assert.ErrorContains(t,
|
|
g.RaceRelation(UnknownRace, Race_0.Name, "Peace"),
|
|
e.GenericErrorText(e.ErrInputUnknownRace))
|
|
assert.ErrorContains(t,
|
|
g.RaceRelation(Race_0.Name, Race_Extinct.Name, "War"),
|
|
e.GenericErrorText(e.ErrRaceExtinct))
|
|
assert.ErrorContains(t,
|
|
g.RaceRelation(Race_Extinct.Name, Race_0.Name, "War"),
|
|
e.GenericErrorText(e.ErrRaceExtinct))
|
|
}
|
|
|
|
func TestRaceQuit(t *testing.T) {
|
|
c, g := newCache()
|
|
|
|
assert.ErrorContains(t,
|
|
g.RaceQuit(UnknownRace),
|
|
e.GenericErrorText(e.ErrInputUnknownRace))
|
|
|
|
assert.ErrorContains(t,
|
|
g.RaceQuit(Race_Extinct.Name),
|
|
e.GenericErrorText(e.ErrRaceExtinct))
|
|
|
|
assert.NoError(t, g.RaceQuit(Race_0.Name))
|
|
assert.Equal(t, 3, int(c.Race(Race_0_idx).TTL))
|
|
}
|
|
|
|
func TestRaceID(t *testing.T) {
|
|
c, g := newCache()
|
|
|
|
c.Race(Race_0_idx).TTL = 9
|
|
|
|
_, err := g.RaceID(UnknownRace)
|
|
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
|
|
|
_, err = g.RaceID(Race_Extinct.Name)
|
|
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrRaceExtinct))
|
|
|
|
id, err := g.RaceID(Race_0.Name)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, Race_0_ID, id)
|
|
}
|
|
|
|
// TestBanishReleasesAssets checks that an administratively banished race only
|
|
// gets flagged extinct, and its planets and ships are released during turn
|
|
// generation; a second pass is a no-op.
|
|
func TestBanishReleasesAssets(t *testing.T) {
|
|
c, g := newCache()
|
|
c.CreateShipsUnsafe_T(Race_1_idx, c.MustShipClass(Race_1_idx, Race_1_Gunship).ID, R1_Planet_1_num, 3)
|
|
assert.True(t, c.MustPlanet(R1_Planet_1_num).OwnedBy(Race_1_ID))
|
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 1)
|
|
|
|
assert.NoError(t, g.RaceBanish(Race_1.Name))
|
|
assert.True(t, c.Race(Race_1_idx).Extinct)
|
|
assert.True(t, c.MustPlanet(R1_Planet_1_num).OwnedBy(Race_1_ID), "still owned until the turn runs")
|
|
|
|
c.TurnWipeExtinctRaces()
|
|
assert.False(t, c.MustPlanet(R1_Planet_1_num).Owned())
|
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 0)
|
|
|
|
// Idempotent: re-running over an already-wiped (asset-less) race is a no-op.
|
|
c.TurnWipeExtinctRaces()
|
|
assert.False(t, c.MustPlanet(R1_Planet_1_num).Owned())
|
|
}
|
|
|
|
// TestIdleRaceWipedOnTimeout guards that a still-active race whose TTL ran out
|
|
// (idle timeout or quit) is still wiped after the iterator change.
|
|
func TestIdleRaceWipedOnTimeout(t *testing.T) {
|
|
c, _ := newCache()
|
|
c.CreateShipsUnsafe_T(Race_1_idx, c.MustShipClass(Race_1_idx, Race_1_Gunship).ID, R1_Planet_1_num, 1)
|
|
c.Race(Race_1_idx).TTL = 0
|
|
assert.False(t, c.Race(Race_1_idx).Extinct)
|
|
|
|
c.TurnWipeExtinctRaces()
|
|
assert.True(t, c.Race(Race_1_idx).Extinct)
|
|
assert.False(t, c.MustPlanet(R1_Planet_1_num).Owned())
|
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 0)
|
|
}
|