6ec1098f15
Bring the report's foreign-group and foreign-class visibility in line with the rules (game/rules.txt "Движение" and the report sections): - incoming groups (heading to one of the recipient's planets) are shown only within the recipient's visibility range (driveTech*30); beyond it a group is hidden even though it is inbound; - the unidentified-group list now uses the visibility range (it used the flight range, driveTech*40), excludes groups heading to the recipient's planets (those belong to the incoming list), and reports each group once (it previously emitted an entry per in-range owned planet); - ship classes met in a battle the recipient took part in or witnessed now appear in OtherShipClass, with the design looked up from the owner race's ship types (the battle report carries only the class name). The rules already describe this behaviour and the report wire shape is unchanged, so no documentation change. Tests added for all three. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
168 lines
6.3 KiB
Go
168 lines
6.3 KiB
Go
package controller_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"galaxy/game/internal/model/game"
|
|
"galaxy/model/report"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestReportRace(t *testing.T) {
|
|
c, _ := newCache()
|
|
|
|
c.TurnCalculateVotes()
|
|
|
|
rep := c.InitReport(2)
|
|
assert.Equal(t, 2, int(rep.Turn))
|
|
|
|
c.ReportRace(Race_0_idx, rep, nil, nil)
|
|
|
|
assert.Equal(t, Race_0.Name, rep.Race)
|
|
assert.Equal(t, Race_0.ID, rep.RaceID)
|
|
assert.Equal(t, 0.1, float64(rep.Votes))
|
|
|
|
for i := range rep.Player {
|
|
p := &rep.Player[i]
|
|
switch p.ID {
|
|
case Race_0_ID:
|
|
assert.Equal(t, Race_0.Name, p.Name)
|
|
assert.Equal(t, 1.1, float64(p.Drive))
|
|
assert.Equal(t, 1.2, float64(p.Weapons))
|
|
assert.Equal(t, 1.3, float64(p.Shields))
|
|
assert.Equal(t, 1.4, float64(p.Cargo))
|
|
assert.Equal(t, 100., float64(p.Population))
|
|
assert.Equal(t, 100., float64(p.Industry))
|
|
assert.Equal(t, 2, int(p.Planets))
|
|
assert.Equal(t, 0.1, float64(p.Votes))
|
|
assert.Equal(t, "-", p.Relation)
|
|
case Race_1_ID:
|
|
assert.Equal(t, Race_1.Name, p.Name)
|
|
assert.Equal(t, 2.1, float64(p.Drive))
|
|
assert.Equal(t, 2.2, float64(p.Weapons))
|
|
assert.Equal(t, 2.3, float64(p.Shields))
|
|
assert.Equal(t, 2.4, float64(p.Cargo))
|
|
assert.Equal(t, 0., float64(p.Population))
|
|
assert.Equal(t, 0., float64(p.Industry))
|
|
assert.Equal(t, 1, int(p.Planets))
|
|
assert.Equal(t, 0., float64(p.Votes))
|
|
assert.Equal(t, "WAR", p.Relation)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReportLocalShipClass(t *testing.T) {
|
|
c, _ := newCache()
|
|
|
|
r := &report.Report{}
|
|
assert.Len(t, r.LocalShipClass, 0)
|
|
|
|
c.ReportLocalShipClass(Race_0_idx, r)
|
|
|
|
assert.Len(t, r.LocalShipClass, 3)
|
|
for i := range r.LocalShipClass {
|
|
assert.NotEmpty(t, r.LocalShipClass[i].Name)
|
|
switch n := r.LocalShipClass[i].Name; n {
|
|
case Cruiser.Name:
|
|
assert.Equal(t, report.F(Cruiser.Drive.F()), r.LocalShipClass[i].Drive)
|
|
assert.Equal(t, Cruiser.Armament, r.LocalShipClass[i].Armament)
|
|
assert.Equal(t, report.F(Cruiser.Weapons.F()), r.LocalShipClass[i].Weapons)
|
|
assert.Equal(t, report.F(Cruiser.Shields.F()), r.LocalShipClass[i].Shields)
|
|
assert.Equal(t, report.F(Cruiser.Cargo.F()), r.LocalShipClass[i].Cargo)
|
|
case Race_0_Gunship:
|
|
assert.Equal(t, report.F(60.), r.LocalShipClass[i].Drive)
|
|
assert.Equal(t, uint(3), r.LocalShipClass[i].Armament)
|
|
assert.Equal(t, report.F(30.), r.LocalShipClass[i].Weapons)
|
|
assert.Equal(t, report.F(100.), r.LocalShipClass[i].Shields)
|
|
assert.Equal(t, report.F(0.), r.LocalShipClass[i].Cargo)
|
|
case Race_0_Freighter:
|
|
assert.Equal(t, report.F(8.), r.LocalShipClass[i].Drive)
|
|
assert.Equal(t, uint(0), r.LocalShipClass[i].Armament)
|
|
assert.Equal(t, report.F(0.), r.LocalShipClass[i].Weapons)
|
|
assert.Equal(t, report.F(2.), r.LocalShipClass[i].Shields)
|
|
assert.Equal(t, report.F(10.), r.LocalShipClass[i].Cargo)
|
|
default:
|
|
assert.Failf(t, "unexpected ship class", "name=%s", n)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestReportIncomingGroupVisibility checks that a group heading to one of the
|
|
// recipient's planets is reported only while within the recipient's visibility
|
|
// range (driveTech*30); beyond it the group is hidden even though it is inbound.
|
|
func TestReportIncomingGroupVisibility(t *testing.T) {
|
|
c, _ := newCache()
|
|
gi := c.CreateShipsUnsafe_T(Race_1_idx, c.MustShipClass(Race_1_idx, Race_1_Gunship).ID, R1_Planet_1_num, 5)
|
|
c.ShipGroup(gi).Destination = R0_Planet_0_num
|
|
|
|
// Within Race_0 visibility (driveTech 1.1 -> 33 ly), near Planet_2 (3,3).
|
|
c.ShipGroup(gi).StateInSpace = &game.InSpace{Origin: R1_Planet_1_num, X: floatRef(5), Y: floatRef(5)}
|
|
rep := c.InitReport(1)
|
|
c.ReportIncomingGroup(Race_0_idx, rep)
|
|
assert.Len(t, rep.IncomingGroup, 1)
|
|
|
|
// Beyond the visibility of every Race_0 planet: hidden.
|
|
c.ShipGroup(gi).StateInSpace = &game.InSpace{Origin: R1_Planet_1_num, X: floatRef(40), Y: floatRef(40)}
|
|
rep = c.InitReport(1)
|
|
c.ReportIncomingGroup(Race_0_idx, rep)
|
|
assert.Len(t, rep.IncomingGroup, 0)
|
|
}
|
|
|
|
// TestReportUnidentifiedGroup checks the three rules for the unidentified list:
|
|
// groups heading to the recipient's planets are excluded (they are "incoming"),
|
|
// only groups within visibility (driveTech*30) appear, and each group appears
|
|
// once even when several owned planets are in range.
|
|
func TestReportUnidentifiedGroup(t *testing.T) {
|
|
c, _ := newCache()
|
|
cls := c.MustShipClass(Race_1_idx, Race_1_Gunship).ID
|
|
|
|
// Not inbound to Race_0, within visibility of BOTH Planet_0 and Planet_2.
|
|
g0 := c.CreateShipsUnsafe_T(Race_1_idx, cls, R1_Planet_1_num, 1)
|
|
c.ShipGroup(g0).Destination = Uninhabited_Planet_3_num
|
|
c.ShipGroup(g0).StateInSpace = &game.InSpace{Origin: R1_Planet_1_num, X: floatRef(5), Y: floatRef(5)}
|
|
|
|
// Inbound to a Race_0 planet -> reported as incoming, not unidentified.
|
|
g1 := c.CreateShipsUnsafe_T(Race_1_idx, cls, R1_Planet_1_num, 1)
|
|
c.ShipGroup(g1).Destination = R0_Planet_0_num
|
|
c.ShipGroup(g1).StateInSpace = &game.InSpace{Origin: R1_Planet_1_num, X: floatRef(5), Y: floatRef(5)}
|
|
|
|
// Not inbound, beyond visibility -> hidden.
|
|
g2 := c.CreateShipsUnsafe_T(Race_1_idx, cls, R1_Planet_1_num, 1)
|
|
c.ShipGroup(g2).Destination = Uninhabited_Planet_3_num
|
|
c.ShipGroup(g2).StateInSpace = &game.InSpace{Origin: R1_Planet_1_num, X: floatRef(40), Y: floatRef(40)}
|
|
|
|
rep := c.InitReport(1)
|
|
c.ReportUnidentifiedGroup(Race_0_idx, rep)
|
|
assert.Len(t, rep.UnidentifiedGroup, 1)
|
|
}
|
|
|
|
// TestReportOtherShipClassFromBattle checks that the class of a foreign ship
|
|
// met in a battle the recipient witnessed is surfaced in OtherShipClass, with
|
|
// its design looked up from the owner race's ship types, while the recipient's
|
|
// own class is skipped.
|
|
func TestReportOtherShipClassFromBattle(t *testing.T) {
|
|
c, _ := newCache()
|
|
br := &report.BattleReport{
|
|
Races: map[int]uuid.UUID{0: Race_0.ID, 1: Race_1.ID},
|
|
Ships: map[int]report.BattleReportGroup{
|
|
0: {Race: Race_1.Name, ClassName: Race_1_Gunship},
|
|
1: {Race: Race_0.Name, ClassName: Race_0_Gunship}, // recipient's own -> skipped
|
|
},
|
|
}
|
|
rep := c.InitReport(1)
|
|
c.ReportOtherShipClass(Race_0_idx, rep, []*report.BattleReport{br})
|
|
|
|
assert.Len(t, rep.OtherShipClass, 1)
|
|
g := rep.OtherShipClass[0]
|
|
assert.Equal(t, Race_1.Name, g.Race)
|
|
assert.Equal(t, Race_1_Gunship, g.Name)
|
|
assert.Equal(t, report.F(60.), g.Drive)
|
|
assert.Equal(t, uint(3), g.Armament)
|
|
assert.Equal(t, report.F(30.), g.Weapons)
|
|
assert.Equal(t, report.F(100.), g.Shields)
|
|
assert.Equal(t, report.F(0.), g.Cargo)
|
|
assert.Equal(t, report.F(220.), g.Mass)
|
|
}
|