@@ 0,0 1,77 @@
+package main
+
+// ReactorCap is the maximum total power that can be distributed across all
+// ship systems at any time.
+const ReactorCap = 8
+
+// System holds the power state of a single ship system.
+// Instances are indexed in a slice by int(RoomRole) so that callers can do
+// a direct O(1) lookup when the role is known; addPower / removePower still
+// do a linear scan by Role field for safety.
+type System struct {
+ Role RoomRole
+ PowerLevel int
+ MaxLevel int
+}
+
+// NewStartingSystems returns a 5-element slice indexed by int(RoomRole).
+// All systems start at PowerLevel 0.
+func NewStartingSystems() []System {
+ return []System{
+ int(RolePilot): {Role: RolePilot, PowerLevel: 0, MaxLevel: 1},
+ int(RoleWeapons): {Role: RoleWeapons, PowerLevel: 0, MaxLevel: 4},
+ int(RoleShields): {Role: RoleShields, PowerLevel: 0, MaxLevel: 4},
+ int(RoleMedBay): {Role: RoleMedBay, PowerLevel: 0, MaxLevel: 2},
+ int(RoleEngines): {Role: RoleEngines, PowerLevel: 0, MaxLevel: 4},
+ }
+}
+
+// reactorUsed returns the sum of PowerLevel across all systems.
+func reactorUsed(sys []System) int {
+ total := 0
+ for _, s := range sys {
+ total += s.PowerLevel
+ }
+ return total
+}
+
+// addPower attempts to raise the PowerLevel of the system with the given role
+// by 1. It returns false (no-op) if:
+// - the system is already at its MaxLevel (IV4), or
+// - the reactor would exceed reactorCap (IV3).
+//
+// Respects IV2: this is the sole mutator of System.PowerLevel for increments.
+func addPower(sys []System, role RoomRole, reactorCap int) bool {
+ for i := range sys {
+ if sys[i].Role != role {
+ continue
+ }
+ if sys[i].PowerLevel >= sys[i].MaxLevel {
+ return false
+ }
+ if reactorUsed(sys) >= reactorCap {
+ return false
+ }
+ sys[i].PowerLevel++
+ return true
+ }
+ return false
+}
+
+// removePower attempts to lower the PowerLevel of the system with the given
+// role by 1. It returns false (no-op) if the system is already at 0 (IV4).
+//
+// Respects IV2: this is the sole mutator of System.PowerLevel for decrements.
+func removePower(sys []System, role RoomRole) bool {
+ for i := range sys {
+ if sys[i].Role != role {
+ continue
+ }
+ if sys[i].PowerLevel <= 0 {
+ return false
+ }
+ sys[i].PowerLevel--
+ return true
+ }
+ return false
+}
@@ 0,0 1,173 @@
+package main
+
+import "testing"
+
+// TestNewStartingSystems_layout verifies the 5-entry slice is indexed by
+// int(RoomRole) and that each entry carries the correct role and cap.
+func TestNewStartingSystems_layout(t *testing.T) {
+ sys := NewStartingSystems()
+ if len(sys) != 5 {
+ t.Fatalf("expected 5 systems, got %d", len(sys))
+ }
+
+ type want struct {
+ role RoomRole
+ maxLevel int
+ }
+ cases := []want{
+ {RolePilot, 1},
+ {RoleWeapons, 4},
+ {RoleShields, 4},
+ {RoleMedBay, 2},
+ {RoleEngines, 4},
+ }
+
+ for _, w := range cases {
+ idx := int(w.role)
+ s := sys[idx]
+ if s.Role != w.role {
+ t.Errorf("sys[%d].Role = %v, want %v", idx, s.Role, w.role)
+ }
+ if s.MaxLevel != w.maxLevel {
+ t.Errorf("sys[%d].MaxLevel = %d, want %d", idx, s.MaxLevel, w.maxLevel)
+ }
+ if s.PowerLevel != 0 {
+ t.Errorf("sys[%d].PowerLevel = %d, want 0", idx, s.PowerLevel)
+ }
+ }
+}
+
+// TestReactorUsed_zero verifies that fresh systems report zero reactor usage.
+func TestReactorUsed_zero(t *testing.T) {
+ sys := NewStartingSystems()
+ if got := reactorUsed(sys); got != 0 {
+ t.Fatalf("reactorUsed on fresh systems = %d, want 0", got)
+ }
+}
+
+// TestReactorUsed_sums verifies that reactorUsed returns the sum of all PowerLevels.
+func TestReactorUsed_sums(t *testing.T) {
+ sys := NewStartingSystems()
+ // Directly set some levels for this test only (bypassing addPower to isolate the sum logic).
+ sys[int(RolePilot)].PowerLevel = 1
+ sys[int(RoleWeapons)].PowerLevel = 3
+ sys[int(RoleEngines)].PowerLevel = 2
+ want := 1 + 3 + 2
+ if got := reactorUsed(sys); got != want {
+ t.Fatalf("reactorUsed = %d, want %d", got, want)
+ }
+}
+
+// TestAddPower_happy verifies a normal add when below cap and reactor has headroom.
+func TestAddPower_happy(t *testing.T) {
+ sys := NewStartingSystems()
+ ok := addPower(sys, RoleWeapons, ReactorCap)
+ if !ok {
+ t.Fatal("addPower returned false, expected true")
+ }
+ if sys[int(RoleWeapons)].PowerLevel != 1 {
+ t.Fatalf("PowerLevel = %d, want 1", sys[int(RoleWeapons)].PowerLevel)
+ }
+}
+
+// TestAddPower_systemAtCap verifies that adding beyond MaxLevel is rejected.
+func TestAddPower_systemAtCap(t *testing.T) {
+ sys := NewStartingSystems()
+ // Pilot cap is 1; add once to hit the cap.
+ if !addPower(sys, RolePilot, ReactorCap) {
+ t.Fatal("first addPower on Pilot should succeed")
+ }
+ if sys[int(RolePilot)].PowerLevel != 1 {
+ t.Fatalf("expected level 1 after first add, got %d", sys[int(RolePilot)].PowerLevel)
+ }
+ // Now at cap — must be rejected.
+ ok := addPower(sys, RolePilot, ReactorCap)
+ if ok {
+ t.Fatal("addPower at cap returned true, expected false")
+ }
+ if sys[int(RolePilot)].PowerLevel != 1 {
+ t.Fatalf("PowerLevel changed after rejected add: got %d", sys[int(RolePilot)].PowerLevel)
+ }
+}
+
+// TestAddPower_reactorFull verifies that adding power when reactor is exhausted is rejected.
+func TestAddPower_reactorFull(t *testing.T) {
+ sys := NewStartingSystems()
+ // Fill reactor to cap using Weapons (cap 4) and Engines (cap 4).
+ for i := 0; i < 4; i++ {
+ if !addPower(sys, RoleWeapons, ReactorCap) {
+ t.Fatalf("addPower Weapons step %d failed unexpectedly", i)
+ }
+ }
+ for i := 0; i < 4; i++ {
+ if !addPower(sys, RoleEngines, ReactorCap) {
+ t.Fatalf("addPower Engines step %d failed unexpectedly", i)
+ }
+ }
+ if reactorUsed(sys) != ReactorCap {
+ t.Fatalf("expected reactor full (%d), got %d", ReactorCap, reactorUsed(sys))
+ }
+ // Shields is below cap but reactor is full — must be rejected.
+ ok := addPower(sys, RoleShields, ReactorCap)
+ if ok {
+ t.Fatal("addPower with full reactor returned true, expected false")
+ }
+ if sys[int(RoleShields)].PowerLevel != 0 {
+ t.Fatalf("PowerLevel changed on rejected add: got %d", sys[int(RoleShields)].PowerLevel)
+ }
+}
+
+// TestAddPower_invariantsHold verifies IV3 and IV4 across all outcomes of addPower.
+func TestAddPower_invariantsHold(t *testing.T) {
+ checkInvariants := func(sys []System) {
+ t.Helper()
+ used := reactorUsed(sys)
+ if used > ReactorCap {
+ t.Errorf("IV3 violated: reactorUsed %d > ReactorCap %d", used, ReactorCap)
+ }
+ for i, s := range sys {
+ if s.PowerLevel < 0 || s.PowerLevel > s.MaxLevel {
+ t.Errorf("IV4 violated: sys[%d].PowerLevel %d not in [0, %d]", i, s.PowerLevel, s.MaxLevel)
+ }
+ }
+ }
+
+ roles := []RoomRole{RolePilot, RoleWeapons, RoleShields, RoleMedBay, RoleEngines}
+ sys := NewStartingSystems()
+
+ // Repeatedly try to add power to every system in a round-robin until the
+ // reactor is full, then one more round of attempts (all should fail).
+ for round := 0; round < 10; round++ {
+ for _, r := range roles {
+ addPower(sys, r, ReactorCap) //nolint:errcheck — we only care about invariants
+ checkInvariants(sys)
+ }
+ }
+}
+
+// TestRemovePower_happy verifies that removing from a powered system decrements level.
+func TestRemovePower_happy(t *testing.T) {
+ sys := NewStartingSystems()
+ if !addPower(sys, RoleShields, ReactorCap) {
+ t.Fatal("addPower Shields failed")
+ }
+ ok := removePower(sys, RoleShields)
+ if !ok {
+ t.Fatal("removePower returned false, expected true")
+ }
+ if sys[int(RoleShields)].PowerLevel != 0 {
+ t.Fatalf("PowerLevel = %d, want 0", sys[int(RoleShields)].PowerLevel)
+ }
+}
+
+// TestRemovePower_zeroLevel verifies that removing from an unpowered system is rejected.
+func TestRemovePower_zeroLevel(t *testing.T) {
+ sys := NewStartingSystems()
+ ok := removePower(sys, RoleEngines)
+ if ok {
+ t.Fatal("removePower at level 0 returned true, expected false")
+ }
+ if sys[int(RoleEngines)].PowerLevel != 0 {
+ t.Fatalf("PowerLevel changed after rejected remove: got %d", sys[int(RoleEngines)].PowerLevel)
+ }
+}