package main import ( "math" "testing" ) func TestNewStartingCrew_count(t *testing.T) { crew := NewStartingCrew() if len(crew) != 3 { t.Fatalf("expected 3 crew, got %d", len(crew)) } } func TestNewStartingCrew_positions(t *testing.T) { crew := NewStartingCrew() cases := []struct { name string initial rune tileX int tileY int }{ {"Alice", 'A', 4, 10}, {"Bob", 'B', 7, 10}, {"Carol", 'C', 11, 10}, } for i, tc := range cases { c := crew[i] if c.Name != tc.name { t.Errorf("crew[%d].Name = %q, want %q", i, c.Name, tc.name) } if c.Initial != tc.initial { t.Errorf("crew[%d].Initial = %q, want %q", i, c.Initial, tc.initial) } if c.TileX != tc.tileX || c.TileY != tc.tileY { t.Errorf("crew[%d] pos = (%d,%d), want (%d,%d)", i, c.TileX, c.TileY, tc.tileX, tc.tileY) } } // Colors must be pairwise distinct if crew[0].Color == crew[1].Color || crew[1].Color == crew[2].Color || crew[0].Color == crew[2].Color { t.Error("crew colors must be pairwise distinct") } } func TestUpdateCrew_idle(t *testing.T) { crew := NewStartingCrew() before := crew[0] updateCrew(&crew[0], 1.0) if crew[0].TileX != before.TileX || crew[0].TileY != before.TileY { t.Error("idle crew position changed") } if crew[0].MoveT != before.MoveT { t.Error("idle crew MoveT changed") } if len(crew[0].Path) != 0 { t.Error("idle crew path changed") } } func TestUpdateCrew_subTileProgress(t *testing.T) { c := Crew{TileX: 4, TileY: 10, Path: [][2]int{{5, 10}}} dt := 0.1 updateCrew(&c, dt) if c.TileX != 4 || c.TileY != 10 { t.Errorf("tile snapped early: (%d,%d)", c.TileX, c.TileY) } if len(c.Path) != 1 { t.Error("path consumed early") } want := dt / TileTime if math.Abs(c.MoveT-want) > 1e-9 { t.Errorf("MoveT = %v, want ~%v", c.MoveT, want) } } func TestUpdateCrew_tileSnap(t *testing.T) { c := Crew{TileX: 4, TileY: 10, Path: [][2]int{{5, 10}}} updateCrew(&c, TileTime) if c.TileX != 5 || c.TileY != 10 { t.Errorf("expected snap to (5,10), got (%d,%d)", c.TileX, c.TileY) } if len(c.Path) != 0 { t.Errorf("expected empty path after snap, got %v", c.Path) } if c.MoveT != 0 { t.Errorf("expected MoveT == 0 after path exhausted, got %v", c.MoveT) } } func TestUpdateCrew_multiStepOvershoot(t *testing.T) { c := Crew{TileX: 4, TileY: 10, Path: [][2]int{{5, 10}, {6, 10}}} updateCrew(&c, 1.5*TileTime) if c.TileX != 5 || c.TileY != 10 { t.Errorf("expected (5,10), got (%d,%d)", c.TileX, c.TileY) } if len(c.Path) != 1 || c.Path[0] != ([2]int{6, 10}) { t.Errorf("expected path [{6,10}], got %v", c.Path) } if math.Abs(c.MoveT-0.5) > 1e-9 { t.Errorf("MoveT = %v, want ~0.5", c.MoveT) } } func TestUpdateCrew_canonicalIntegral(t *testing.T) { c := Crew{TileX: 4, TileY: 10, Path: [][2]int{{5, 10}, {6, 10}, {7, 10}}} updateCrew(&c, 2.3*TileTime) // TileX and TileY must be integer tile coordinates (always true for int fields, // but verify they equal a known-crossed waypoint) if c.TileX != 6 || c.TileY != 10 { t.Errorf("expected canonical position (6,10), got (%d,%d)", c.TileX, c.TileY) } // MoveT must be strictly < 1 (render-time only, never a full tile) if c.MoveT >= 1.0 { t.Errorf("MoveT must be < 1, got %v", c.MoveT) } }