diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index f5b7937..fe38c14 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -23,7 +23,10 @@ var ( game.TechShields: 1.3, game.TechCargo: 1.4, }, - Relations: []game.RaceRelation{{RaceID: Race_1_ID, Relation: game.RelationWar}}, + Relations: []game.RaceRelation{ + {RaceID: Race_1_ID, Relation: game.RelationWar}, + {RaceID: Race_2_ID, Relation: game.RelationWar}, + }, } Race_1 = game.Race{ ID: Race_1_ID, @@ -36,7 +39,27 @@ var ( game.TechShields: 2.3, game.TechCargo: 2.4, }, - Relations: []game.RaceRelation{{RaceID: Race_0_ID, Relation: game.RelationPeace}}, + Relations: []game.RaceRelation{ + {RaceID: Race_0_ID, Relation: game.RelationPeace}, + {RaceID: Race_2_ID, Relation: game.RelationPeace}, + }, + } + Race_Extinct = game.Race{ + ID: Race_2_ID, + VoteFor: Race_2_ID, + Name: "Race_Extinct", + Extinct: true, + TTL: 0, + Tech: map[game.Tech]game.Float{ + game.TechDrive: 3.1, + game.TechWeapons: 3.2, + game.TechShields: 3.3, + game.TechCargo: 3.4, + }, + Relations: []game.RaceRelation{ + {RaceID: Race_0_ID, Relation: game.RelationPeace}, + {RaceID: Race_1_ID, Relation: game.RelationWar}, + }, } Race_0_ID = uuid.New() @@ -58,6 +81,8 @@ var ( Race_1_Freighter_idx = 1 Race_1_Cruiser_idx = 2 + Race_2_ID = uuid.New() + Uninhabited_Planet_3_num uint = 3 Uninhabited_Planet_4_num uint = 4 @@ -71,6 +96,9 @@ var ( Shields: 15, Cargo: 0, } + + BadEntityName = "Bad(entitty)Name" + UnknownRace = "UnknownRace" ) // [ ] Delete this fake test @@ -92,6 +120,7 @@ func newGame() *game.Game { Race: []game.Race{ Race_0, Race_1, + Race_Extinct, }, Map: game.Map{ Width: 1000, diff --git a/internal/controller/fleet_send_test.go b/internal/controller/fleet_send_test.go index 24d2738..4c3bd25 100644 --- a/internal/controller/fleet_send_test.go +++ b/internal/controller/fleet_send_test.go @@ -51,8 +51,11 @@ func TestSendFleet(t *testing.T) { c.ShipGroup(1).StateInSpace = &game.InSpace{Origin: 2, Range: 1.23} assert.ErrorContains(t, - g.SendFleet("UnknownRace", fleetSending, 2), + g.SendFleet(UnknownRace, fleetSending, 2), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.SendFleet(Race_Extinct.Name, fleetSending, 2), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.SendFleet(Race_0.Name, "UnknownFleet", 2), e.GenericErrorText(e.ErrInputEntityNotExists)) diff --git a/internal/controller/fleet_test.go b/internal/controller/fleet_test.go index 90450a0..a1425fc 100644 --- a/internal/controller/fleet_test.go +++ b/internal/controller/fleet_test.go @@ -14,8 +14,11 @@ func TestJoinShipGroupToFleet(t *testing.T) { c, g := newCache() var groupIndex uint = 1 + fleetOne := "R0_Fleet_one" + fleetTwo := "R0_Fleet_two" + assert.ErrorContains(t, - g.JoinShipGroupToFleet(Race_0.Name, " ", groupIndex, 0), + g.JoinShipGroupToFleet(Race_0.Name, BadEntityName, groupIndex, 0), e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)) assert.ErrorContains(t, @@ -25,6 +28,13 @@ func TestJoinShipGroupToFleet(t *testing.T) { // creating ShipGroup assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 5)) + assert.ErrorContains(t, + g.JoinShipGroupToFleet(UnknownRace, fleetOne, groupIndex, 0), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.JoinShipGroupToFleet(Race_Extinct.Name, fleetOne, groupIndex, 0), + e.GenericErrorText(e.ErrRaceExinct)) + assert.ErrorContains(t, g.JoinShipGroupToFleet(Race_0.Name, "Unnamed", groupIndex, 6), e.GenericErrorText(e.ErrJoinFleetGroupNumberNotEnough)) @@ -32,9 +42,6 @@ func TestJoinShipGroupToFleet(t *testing.T) { // ensure race has no Fleets assert.Len(t, slices.Collect(c.ListFleets(Race_0_idx)), 0) - fleetOne := "R0_Fleet_one" - fleetTwo := "R0_Fleet_two" - assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOne, groupIndex, 0)) fleets := slices.Collect(c.ListFleets(Race_0_idx)) groups := slices.Collect(c.RaceShipGroups(Race_0_idx)) @@ -121,14 +128,30 @@ func TestJoinFleets(t *testing.T) { assert.ErrorContains(t, g.JoinFleets(Race_0.Name, fleetSourceOne, fleetTargetTwo), e.GenericErrorText(e.ErrInputEntityNotExists)) + assert.ErrorContains(t, + g.JoinFleets(UnknownRace, fleetSourceOne, fleetTargetTwo), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.JoinFleets(Race_Extinct.Name, fleetSourceOne, fleetTargetTwo), + e.GenericErrorText(e.ErrRaceExinct)) + + assert.ErrorContains(t, + g.JoinShipGroupToFleet(UnknownRace, fleetSourceOne, 1, 0), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.JoinShipGroupToFleet(Race_Extinct.Name, fleetSourceOne, 1, 0), + e.GenericErrorText(e.ErrRaceExinct)) + assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetSourceOne, 1, 0)) + assert.ErrorContains(t, g.JoinFleets(Race_0.Name, fleetSourceOne, fleetTargetTwo), e.GenericErrorText(e.ErrInputEntityNotExists)) + assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetTargetTwo, 3, 0)) assert.NoError(t, g.JoinFleets(Race_0.Name, fleetSourceOne, fleetTargetTwo)) - assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleetOnPlanet2, 2, 0)) + assert.ErrorContains(t, g.JoinFleets(Race_0.Name, fleetOnPlanet2, fleetTargetTwo), e.GenericErrorText(e.ErrShipsNotOnSamePlanet)) diff --git a/internal/controller/planet.go b/internal/controller/planet.go index a083a58..3a41cf2 100644 --- a/internal/controller/planet.go +++ b/internal/controller/planet.go @@ -43,8 +43,10 @@ func (c *Cache) PlanetProduction(ri int, number int, prod game.ProductionType, s return e.NewEntityNotOwnedError("planet #%d", number) } var subjectID *uuid.UUID - if (prod == game.ResearchScience || prod == game.ProductionShip) && subj == "" { - return e.NewEntityTypeNameValidationError("%s=%q", prod, subj) + if prod == game.ResearchScience || prod == game.ProductionShip { + if _, ok := util.ValidateTypeName(subj); !ok { + return e.NewEntityTypeNameValidationError("%s=%q", prod, subj) + } } if prod == game.ResearchScience { diff --git a/internal/controller/planet_test.go b/internal/controller/planet_test.go index b8c929d..ea2e2e1 100644 --- a/internal/controller/planet_test.go +++ b/internal/controller/planet_test.go @@ -20,8 +20,11 @@ func TestRenamePlanet(t *testing.T) { assert.Equal(t, "Home_World", c.MustPlanet(R0_Planet_0_num).Name) assert.ErrorContains(t, - g.RenamePlanet("UnknownRace", int(R0_Planet_0_num), "Home_World"), + g.RenamePlanet(UnknownRace, int(R0_Planet_0_num), "Home_World"), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.RenamePlanet(Race_Extinct.Name, int(R0_Planet_0_num), "Home_World"), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.RenamePlanet(Race_0.Name, -1, "Home_World"), e.GenericErrorText(e.ErrInputPlanetNumber)) @@ -97,8 +100,11 @@ func TestPlanetProduction(t *testing.T) { pn = int(R0_Planet_2_num) assert.ErrorContains(t, - g.PlanetProduction("UnknownRace", pn, "DRIVE", ""), + g.PlanetProduction(UnknownRace, pn, "DRIVE", ""), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.PlanetProduction(Race_Extinct.Name, pn, "DRIVE", ""), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.PlanetProduction(Race_0.Name, pn, "Hyperdrive", ""), e.GenericErrorText(e.ErrInputProductionInvalid)) diff --git a/internal/controller/race_test.go b/internal/controller/race_test.go index d05c70d..16b2804 100644 --- a/internal/controller/race_test.go +++ b/internal/controller/race_test.go @@ -18,8 +18,18 @@ func TestGiveVotes(t *testing.T) { 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.GiveVotes("UnknownRace", Race_1.Name), e.GenericErrorText(e.ErrInputUnknownRace)) - assert.ErrorContains(t, g.GiveVotes(Race_0.Name, "UnknownRace"), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.GiveVotes(UnknownRace, Race_1.Name), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.GiveVotes(Race_0.Name, UnknownRace), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.GiveVotes(Race_0.Name, Race_Extinct.Name), + e.GenericErrorText(e.ErrRaceExinct)) + assert.ErrorContains(t, + g.GiveVotes(Race_Extinct.Name, Race_1.Name), + e.GenericErrorText(e.ErrRaceExinct)) } func TestRelation(t *testing.T) { @@ -31,13 +41,31 @@ func TestRelation(t *testing.T) { 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)) - c.WipeRace(Race_1_idx) - assert.ErrorContains(t, - g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar), + g.UpdateRelation(Race_0.Name, UnknownRace, game.RelationWar), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.UpdateRelation(UnknownRace, Race_0.Name, game.RelationWar), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.UpdateRelation(Race_0.Name, Race_Extinct.Name, game.RelationWar), e.GenericErrorText(e.ErrRaceExinct)) - assert.ErrorContains(t, - g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar), + g.UpdateRelation(Race_Extinct.Name, Race_0.Name, game.RelationWar), e.GenericErrorText(e.ErrRaceExinct)) } + +func TestQuitGame(t *testing.T) { + c, g := newCache() + + assert.ErrorContains(t, + g.QuitGame(UnknownRace), + e.GenericErrorText(e.ErrInputUnknownRace)) + + assert.ErrorContains(t, + g.QuitGame(Race_Extinct.Name), + e.GenericErrorText(e.ErrRaceExinct)) + + assert.NoError(t, g.QuitGame(Race_0.Name)) + assert.Equal(t, 3, int(c.Race(Race_0_idx).TTL)) +} diff --git a/internal/controller/route.go b/internal/controller/route.go index 47fd613..b5a70c6 100644 --- a/internal/controller/route.go +++ b/internal/controller/route.go @@ -66,7 +66,6 @@ func (c *Cache) RemovePlanetRoute(rt game.RouteType, origin uint) { } } -// TODO: NOT IN THIS FUNC: remove routes if planet became uninhabited (bombing, quit game, etc) func (c *Cache) SendRoutedGroups() { for pi := range c.g.Map.Planet { if len(c.g.Map.Planet[pi].Route) == 0 { diff --git a/internal/controller/route_test.go b/internal/controller/route_test.go index 37aaa34..bfe99fe 100644 --- a/internal/controller/route_test.go +++ b/internal/controller/route_test.go @@ -44,8 +44,11 @@ func TestSetRoute(t *testing.T) { assert.Contains(t, c.MustPlanet(0).Route, game.RouteEmpty) assert.ErrorContains(t, - g.SetRoute("UnknownRace", "COL", 0, 2), + g.SetRoute(UnknownRace, "COL", 0, 2), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.SetRoute(Race_Extinct.Name, "COL", 0, 2), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.SetRoute(Race_0.Name, "IND", 0, 2), e.GenericErrorText(e.ErrInputCargoTypeInvalid)) @@ -79,8 +82,11 @@ func TestRemoveRoute(t *testing.T) { assert.NotContains(t, c.MustPlanet(2).Route, game.RouteEmpty) assert.ErrorContains(t, - g.RemoveRoute("UnknownRace", "COL", 0), + g.RemoveRoute(UnknownRace, "COL", 0), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.RemoveRoute(Race_Extinct.Name, "COL", 0), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.RemoveRoute(Race_0.Name, "IND", 0), e.GenericErrorText(e.ErrInputCargoTypeInvalid)) diff --git a/internal/controller/science_test.go b/internal/controller/science_test.go index 1fd0982..91aa7ae 100644 --- a/internal/controller/science_test.go +++ b/internal/controller/science_test.go @@ -11,7 +11,6 @@ import ( ) func TestCreateScience(t *testing.T) { - // TODO: test on dead race c, g := newCache() first := "Drive_Shields" second := "Hyperdrive" @@ -28,10 +27,13 @@ func TestCreateScience(t *testing.T) { assert.Equal(t, 0., sc.Cargo.F()) assert.ErrorContains(t, - g.CreateScience("UnknownRace", second, 0.4, 0, 0.6, 0), + g.CreateScience(UnknownRace, second, 0.4, 0, 0.6, 0), e.GenericErrorText(e.ErrInputUnknownRace)) assert.ErrorContains(t, - g.CreateScience(Race_0.Name, " ", 0.4, 0, 0.6, 0), + g.CreateScience(Race_Extinct.Name, second, 0.4, 0, 0.6, 0), + e.GenericErrorText(e.ErrRaceExinct)) + assert.ErrorContains(t, + g.CreateScience(Race_0.Name, BadEntityName, 0.4, 0, 0.6, 0), e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)) assert.ErrorContains(t, g.CreateScience(Race_0.Name, first, 0.4, 0, 0.6, 0), @@ -73,9 +75,6 @@ func TestCreateScience(t *testing.T) { } func TestDeleteScience(t *testing.T) { - // TODO: test on dead race - // TODO: test with existing ship group - // TODO: test with planet production busy with science c, g := newCache() first := "Drive_Shields" second := "Hyperdrive" @@ -90,8 +89,11 @@ func TestDeleteScience(t *testing.T) { g.PlanetProduction(Race_0.Name, int(R0_Planet_0_num), "SCIENCE", second) assert.ErrorContains(t, - g.DeleteScience("UnknownRace", second), + g.DeleteScience(UnknownRace, second), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.DeleteScience(Race_Extinct.Name, second), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.DeleteScience(Race_0.Name, first), e.GenericErrorText(e.ErrInputEntityNotExists)) diff --git a/internal/controller/ship_class.go b/internal/controller/ship_class.go index 155d1f3..7b49294 100644 --- a/internal/controller/ship_class.go +++ b/internal/controller/ship_class.go @@ -13,7 +13,7 @@ import ( func (c *Cache) CreateShipType(ri int, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error { c.validateRaceIndex(ri) - if err := checkShipTypeValues(drive, ammo, weapons, shileds, cargo); err != nil { + if err := validateShipTypeValues(drive, ammo, weapons, shileds, cargo); err != nil { return err } n, ok := util.ValidateTypeName(typeName) @@ -37,24 +37,24 @@ func (c *Cache) CreateShipType(ri int, typeName string, drive float64, ammo int, return nil } -func (c *Cache) MergeShipType(ri int, name, targetName string) error { +func (c *Cache) MergeShipType(ri int, sourceName, targetName string) error { c.validateRaceIndex(ri) - st, sti, ok := c.ShipClass(ri, name) + sourceClass, sti, ok := c.ShipClass(ri, sourceName) if !ok { - return e.NewEntityNotExistsError("source ship type %q", name) + return e.NewEntityNotExistsError("source ship type %q", sourceName) } - tt, _, ok := c.ShipClass(ri, targetName) + targetClass, _, ok := c.ShipClass(ri, targetName) if !ok { - return e.NewEntityNotExistsError("target ship type %q", name) + return e.NewEntityNotExistsError("target ship type %q", sourceName) } - if st.Name == tt.Name { + if sourceClass.Name == targetClass.Name { return e.NewEntityTypeNameEqualityError("ship type %q", targetName) } - if !st.Equal(*tt) { + if !sourceClass.Equal(*targetClass) { return e.NewMergeShipTypeNotEqualError() } @@ -63,16 +63,16 @@ func (c *Cache) MergeShipType(ri int, name, targetName string) error { if c.g.Map.Planet[pl].OwnedBy(c.g.Race[ri].ID) && c.g.Map.Planet[pl].Production.Type == game.ProductionShip && c.g.Map.Planet[pl].Production.SubjectID != nil && - *c.g.Map.Planet[pl].Production.SubjectID == st.ID { + *c.g.Map.Planet[pl].Production.SubjectID == sourceClass.ID { - c.g.Map.Planet[pl].Production.SubjectID = &tt.ID + c.g.Map.Planet[pl].Production.SubjectID = &targetClass.ID } } // switch ship groups to the new type for sg := range c.g.ShipGroups { - if c.g.ShipGroups[sg].OwnerID == c.g.Race[ri].ID && c.g.ShipGroups[sg].TypeID == st.ID { - c.g.ShipGroups[sg].TypeID = tt.ID + if c.g.ShipGroups[sg].OwnerID == c.g.Race[ri].ID && c.g.ShipGroups[sg].TypeID == sourceClass.ID { + c.g.ShipGroups[sg].TypeID = targetClass.ID } } @@ -141,7 +141,6 @@ func (c *Cache) ShipClass(ri int, name string) (*game.ShipType, int, bool) { } func (c *Cache) ShipType(ri int, ID uuid.UUID) (*game.ShipType, bool) { - // TODO: cache + invalidate c.validateRaceIndex(ri) for i := range c.g.Race[ri].ShipTypes { if c.g.Race[ri].ShipTypes[i].ID == ID { @@ -155,10 +154,10 @@ func (c *Cache) MustShipType(ri int, ID uuid.UUID) *game.ShipType { if v, ok := c.ShipType(ri, ID); ok { return v } - panic(fmt.Sprintf("ship_class not found: race_id=%d id=%v", ri, ID)) + panic(fmt.Sprintf("ship class not found: race_idx=%d id=%v", ri, ID)) } -func checkShipTypeValues(d float64, a int, w, s, c float64) error { +func validateShipTypeValues(d float64, a int, w, s, c float64) error { if !checkShipTypeValueDWSC(d) { return e.NewDriveValueError(d) } diff --git a/internal/controller/ship_class_test.go b/internal/controller/ship_class_test.go new file mode 100644 index 0000000..78670b4 --- /dev/null +++ b/internal/controller/ship_class_test.go @@ -0,0 +1,96 @@ +package controller_test + +import ( + "slices" + "testing" + + e "github.com/iliadenisov/galaxy/internal/error" + "github.com/stretchr/testify/assert" +) + +func TestCreateShipClass(t *testing.T) { + c, g := newCache() + + assert.NoError(t, g.CreateShipType(Race_0.Name, "Random", 1, 3, 5, 4, 2)) + ships := slices.Collect(c.ListShipTypes(Race_0_idx)) + assert.Len(t, ships, 4) + st := ships[3] + assert.Equal(t, 1., float64(st.Drive)) + assert.Equal(t, 3, int(st.Armament)) + assert.Equal(t, 5., float64(st.Weapons)) + assert.Equal(t, 4., float64(st.Shields)) + assert.Equal(t, 2., float64(st.Cargo)) + + assert.ErrorContains(t, + g.CreateShipType(Race_0.Name, Race_0_Gunship, 1, 0, 0, 0, 0), + e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate)) + assert.ErrorContains(t, + g.CreateShipType(UnknownRace, "Drone", 1, 0, 0, 0, 0), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.CreateShipType(Race_Extinct.Name, "Drone", 1, 0, 0, 0, 0), + e.GenericErrorText(e.ErrRaceExinct)) + assert.ErrorContains(t, + g.CreateShipType(Race_0.Name, BadEntityName, 1, 0, 0, 0, 0), + e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)) +} + +func TestMergeShipClass(t *testing.T) { + c, g := newCache() + + assert.Len(t, c.ShipTypes(Race_0_idx), 3) + + assert.NoError(t, g.CreateShipType(Race_0.Name, "Drone", 1, 0, 0, 0, 0)) + assert.Len(t, c.ShipTypes(Race_0_idx), 4) + assert.NoError(t, g.CreateShipType(Race_0.Name, "Spy", 1, 0, 0, 0, 0)) + assert.Len(t, c.ShipTypes(Race_0_idx), 5) + assert.NoError(t, g.CreateShipType(Race_0.Name, "Surfer", 15, 15, 15, 0, 1)) + assert.Len(t, c.ShipTypes(Race_0_idx), 6) + + assert.ErrorContains(t, + g.MergeShipType(Race_0.Name, "Sky", "Drone"), + e.GenericErrorText(e.ErrInputEntityNotExists)) + assert.ErrorContains(t, + g.MergeShipType(Race_0.Name, "Spy", "Elephant"), + e.GenericErrorText(e.ErrInputEntityNotExists)) + assert.ErrorContains(t, + g.MergeShipType(Race_Extinct.Name, "Spy", "Drone"), + e.GenericErrorText(e.ErrRaceExinct)) + assert.ErrorContains(t, + g.MergeShipType(Race_0.Name, "Spy", "Spy"), + e.GenericErrorText(e.ErrInputEntityTypeNameEquality)) + + assert.NoError(t, g.MergeShipType(Race_0.Name, "Spy", "Drone")) + assert.Len(t, c.ShipTypes(Race_0_idx), 5) + + assert.ErrorContains(t, + g.MergeShipType(Race_0.Name, "Drone", "Surfer"), + e.GenericErrorText(e.ErrMergeShipTypeNotEqual)) +} + +func TestDeleteShipClass(t *testing.T) { + c, g := newCache() + + assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1)) + assert.NoError(t, g.CreateShipType(Race_0.Name, "Drone", 1, 0, 0, 0, 0)) + g.PlanetProduction(Race_0.Name, int(R0_Planet_0_num), "SHIP", "Drone") + + assert.ErrorContains(t, + g.DeleteShipType(UnknownRace, Race_0_Freighter), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.DeleteShipType(Race_Extinct.Name, Race_0_Freighter), + e.GenericErrorText(e.ErrRaceExinct)) + assert.ErrorContains(t, + g.DeleteShipType(Race_0.Name, "Elephant"), + e.GenericErrorText(e.ErrInputEntityNotExists)) + assert.ErrorContains(t, + g.DeleteShipType(Race_0.Name, "Drone"), + e.GenericErrorText(e.ErrDeleteShipTypePlanetProduction)) + + assert.NoError(t, g.DeleteShipType(Race_0.Name, Race_0_Freighter)) + + assert.ErrorContains(t, + g.DeleteShipType(Race_0.Name, Race_0_Gunship), + e.GenericErrorText(e.ErrDeleteShipTypeExistingGroup)) +} diff --git a/internal/controller/ship_group.go b/internal/controller/ship_group.go index cece2e8..d50d83e 100644 --- a/internal/controller/ship_group.go +++ b/internal/controller/ship_group.go @@ -1,6 +1,7 @@ package controller import ( + "cmp" "fmt" "iter" "maps" @@ -551,15 +552,21 @@ func (c *Cache) listShipGroups(ri int) iter.Seq[*game.ShipGroup] { } } -// TODO: order upgradable groups by cost descending, describe in Rules func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup] { return func(yield func(*game.ShipGroup) bool) { + result := make([]int, 0) for sg := range c.g.ShipGroups { // number checked for further sanity after battles if c.g.ShipGroups[sg].Number > 0 && c.g.ShipGroups[sg].Destination == planetNumber && c.g.ShipGroups[sg].State() == game.StateUpgrade { - if !yield(&c.g.ShipGroups[sg]) { - break - } + result = append(result, sg) + } + } + slices.SortFunc(result, func(a, b int) int { + return cmp.Compare(c.g.ShipGroups[b].StateUpgrade.Cost(), c.g.ShipGroups[a].StateUpgrade.Cost()) + }) + for i := range result { + if !yield(&c.g.ShipGroups[result[i]]) { + return } } } diff --git a/internal/controller/ship_group_send_test.go b/internal/controller/ship_group_send_test.go index aa97191..7b46ed1 100644 --- a/internal/controller/ship_group_send_test.go +++ b/internal/controller/ship_group_send_test.go @@ -23,8 +23,11 @@ func TestSendGroup(t *testing.T) { assert.NoError(t, c.CreateShips(Race_0_idx, "Fortress", R0_Planet_0_num, 1)) assert.ErrorContains(t, - g.SendGroup("UnknownRace", 1, 2, 0), + g.SendGroup(UnknownRace, 1, 2, 0), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.SendGroup(Race_Extinct.Name, 1, 2, 0), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.SendGroup(Race_0.Name, 555, 2, 0), e.GenericErrorText(e.ErrInputEntityNotExists)) diff --git a/internal/controller/ship_group_test.go b/internal/controller/ship_group_test.go index c71ed9f..b3e766c 100644 --- a/internal/controller/ship_group_test.go +++ b/internal/controller/ship_group_test.go @@ -36,7 +36,7 @@ func TestCreateShips(t *testing.T) { } func TestJoinEqualGroups(t *testing.T) { - c, _ := newCache() + c, g := newCache() assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 1)) // 1 -> 2 assert.NoError(t, c.CreateShips(Race_1_idx, Race_1_Freighter, R1_Planet_1_num, 1)) @@ -57,7 +57,14 @@ func TestJoinEqualGroups(t *testing.T) { assert.NoError(t, c.CreateShips(1, Race_1_Freighter, R1_Planet_1_num, 1)) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 3) - c.JoinEqualGroups(Race_0_idx) + assert.ErrorContains(t, + g.JoinEqualGroups(UnknownRace), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.JoinEqualGroups(Race_Extinct.Name), + e.GenericErrorText(e.ErrRaceExinct)) + + assert.NoError(t, g.JoinEqualGroups(Race_0.Name)) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 3) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 4) @@ -104,8 +111,11 @@ func TestBreakGroup(t *testing.T) { assert.NoError(t, g.JoinShipGroupToFleet(Race_0.Name, fleet, 1, 0)) assert.ErrorContains(t, - g.BreakGroup("UnknownRace", 1, 0), + g.BreakGroup(UnknownRace, 1, 0), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.BreakGroup(Race_Extinct.Name, 1, 0), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.BreakGroup(Race_0.Name, 555, 0), e.GenericErrorText(e.ErrInputEntityNotExists)) @@ -179,11 +189,17 @@ func TestTransferGroup(t *testing.T) { assert.Len(t, slices.Collect(c.RaceShipGroups(Race_1_idx)), 1) assert.ErrorContains(t, - g.TransferGroup("UnknownRace", Race_1.Name, 2, 0), + g.TransferGroup(UnknownRace, Race_1.Name, 2, 0), e.GenericErrorText(e.ErrInputUnknownRace)) assert.ErrorContains(t, - g.TransferGroup(Race_0.Name, "UnknownRace", 2, 0), + g.TransferGroup(Race_0.Name, UnknownRace, 2, 0), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.TransferGroup(Race_0.Name, Race_Extinct.Name, 2, 0), + e.GenericErrorText(e.ErrRaceExinct)) + assert.ErrorContains(t, + g.TransferGroup(Race_Extinct.Name, Race_1.Name, 2, 0), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.TransferGroup(Race_0.Name, Race_0.Name, 2, 0), e.GenericErrorText(e.ErrInputSameRace)) @@ -273,8 +289,11 @@ func TestLoadCargo(t *testing.T) { // tests assert.ErrorContains(t, - g.LoadCargo("UnknownRace", 1, game.CargoMaterial.String(), 0, 0), + g.LoadCargo(UnknownRace, 1, game.CargoMaterial.String(), 0, 0), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.LoadCargo(Race_Extinct.Name, 1, game.CargoMaterial.String(), 0, 0), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.LoadCargo(Race_0.Name, 1, "GOLD", 0, 0), e.GenericErrorText(e.ErrInputCargoTypeInvalid)) @@ -401,8 +420,11 @@ func TestUnloadCargo(t *testing.T) { // tests assert.ErrorContains(t, - g.UnloadCargo("UnknownRace", 1, 0, 0), + g.UnloadCargo(UnknownRace, 1, 0, 0), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.UnloadCargo(Race_Extinct.Name, 1, 0, 0), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.UnloadCargo(Race_0.Name, 555, 0, 0), e.GenericErrorText(e.ErrInputEntityNotExists)) @@ -495,8 +517,11 @@ func TestDisassembleGroup(t *testing.T) { // tests assert.ErrorContains(t, - g.DisassembleGroup("UnknownRace", 1, 0), + g.DisassembleGroup(UnknownRace, 1, 0), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.DisassembleGroup(Race_Extinct.Name, 1, 0), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.DisassembleGroup(Race_0.Name, 555, 0), e.GenericErrorText(e.ErrInputEntityNotExists)) diff --git a/internal/controller/ship_group_upgrade_test.go b/internal/controller/ship_group_upgrade_test.go index 8b60427..f0765d1 100644 --- a/internal/controller/ship_group_upgrade_test.go +++ b/internal/controller/ship_group_upgrade_test.go @@ -125,8 +125,11 @@ func TestUpgradeGroup(t *testing.T) { c.ShipGroup(2).Destination = R1_Planet_1_num assert.ErrorContains(t, - g.UpgradeGroup("UnknownRace", 1, "DRIVE", 0, 0), + g.UpgradeGroup(UnknownRace, 1, "DRIVE", 0, 0), e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.UpgradeGroup(Race_Extinct.Name, 1, "DRIVE", 0, 0), + e.GenericErrorText(e.ErrRaceExinct)) assert.ErrorContains(t, g.UpgradeGroup(Race_0.Name, 555, "DRIVE", 0, 0), e.GenericErrorText(e.ErrInputEntityNotExists)) diff --git a/internal/game/cmd_planet_test.go b/internal/game/cmd_planet_test.go index 7473427..18e302f 100644 --- a/internal/game/cmd_planet_test.go +++ b/internal/game/cmd_planet_test.go @@ -44,7 +44,7 @@ func TestRenamePlanet(t *testing.T) { assert.GreaterOrEqual(t, ri, 0) otherRace := cg.Race[ri].Name - err = game.RenamePlanet(p, unknownRaceName, number, newName) // TODO: test actual rip race + err = game.RenamePlanet(p, unknownRaceName, number, newName) assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace)) err = game.RenamePlanet(p, race, number, "") assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)) diff --git a/internal/game/cmd_ship_type_test.go b/internal/game/cmd_ship_type_test.go index 4b304a2..a8c7e2c 100644 --- a/internal/game/cmd_ship_type_test.go +++ b/internal/game/cmd_ship_type_test.go @@ -18,7 +18,7 @@ func TestCreateShipType(t *testing.T) { c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) { err := game.DeleteShipType(p, race, typeName) assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists)) - err = game.CreateShipType(p, unknownRaceName, " "+typeName+" ", 1, 0, 0, 0, 0) // TODO: test on dead race + err = game.CreateShipType(p, unknownRaceName, " "+typeName+" ", 1, 0, 0, 0, 0) assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace)) err = game.CreateShipType(p, race, " "+typeName+" ", 1, 0, 0, 0, 0) assert.NoError(t, err) @@ -30,8 +30,7 @@ func TestCreateShipType(t *testing.T) { assert.Equal(t, st[0].Shields.F(), 0.) assert.Equal(t, st[0].Cargo.F(), 0.) assert.Equal(t, st[0].Armament, uint(0)) - // TODO: test with existing ship group - err = game.DeleteShipType(p, unknownRaceName, typeName) // TODO: test on dead race + err = game.DeleteShipType(p, unknownRaceName, typeName) assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace)) err = game.DeleteShipType(p, race, typeName) assert.NoError(t, err) @@ -111,6 +110,5 @@ func TestMergeShipType(t *testing.T) { assert.Len(t, st, 2) err = game.MergeShipType(p, race, "Drone", "Cruiser") assert.ErrorContains(t, err, e.GenericErrorText(e.ErrMergeShipTypeNotEqual)) - // TODO: test group/production changed }) }