package validate import ( "os" "path/filepath" "strconv" "strings" "testing" "thehouseoficarus/internal/world" ) func writeGridRoom(t *testing.T, dir string, id int, body string) { t.Helper() rooms := filepath.Join(dir, "rooms") if err := os.MkdirAll(rooms, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(rooms, strconv.Itoa(id)+".yaml"), []byte(body), 0o644); err != nil { t.Fatal(err) } } func runGridCheck(t *testing.T, rooms map[int]string, roots ...int) []Issue { t.Helper() dir := t.TempDir() for id, body := range rooms { writeGridRoom(t, dir, id, body) } return validateRoomGrid(Source{World: world.New(dir), RootRooms: roots}) } func containsMsg(issues []Issue, substr string) bool { for _, iss := range issues { if strings.Contains(iss.Message, substr) { return true } } return false } func TestGridCleanLayout(t *testing.T) { // A consistent 2x2 block: 4 is reached the same way from 2 and from 3. rooms := map[int]string{ 1: "exits:\n south: {room: 3}\n east: {room: 2}\n", 2: "exits:\n south: {room: 4}\n", 3: "exits:\n east: {room: 4}\n", 4: "name: corner\n", } if issues := runGridCheck(t, rooms, 1); len(issues) != 0 { t.Errorf("expected no grid issues, got: %+v", issues) } } func TestGridOverlap(t *testing.T) { // rooms 4 and 5 both resolve to grid (1,1). rooms := map[int]string{ 1: "exits:\n south: {room: 3}\n east: {room: 2}\n", 2: "exits:\n south: {room: 5}\n", 3: "exits:\n east: {room: 4}\n", 4: "name: four\n", 5: "name: five\n", } issues := runGridCheck(t, rooms, 1) if !containsMsg(issues, "Grid overlap") { t.Errorf("expected a grid overlap, got: %+v", issues) } for _, iss := range issues { if iss.Level != "ERROR" { t.Errorf("grid issues should be ERROR, got %q", iss.Level) } } } func TestGridTwist(t *testing.T) { // room 3 is forced onto two different cells. rooms := map[int]string{ 1: "exits:\n south: {room: 4}\n east: {room: 2}\n", 2: "exits:\n east: {room: 3}\n", 4: "exits:\n east: {room: 3}\n", 3: "name: three\n", } issues := runGridCheck(t, rooms, 1) if !containsMsg(issues, "Grid twist") { t.Errorf("expected a grid twist, got: %+v", issues) } } func TestGridMultiPlaneViaUpDown(t *testing.T) { // Room 1 at (0,0,0) goes up to room 10 at (0,0,1). Rooms 13 and 14 both // resolve to (1,1,1) — 3D overlap on the upper level. rooms := map[int]string{ 1: "exits:\n up: {room: 10}\n", 10: "exits:\n south: {room: 12}\n east: {room: 11}\n", 11: "exits:\n south: {room: 14}\n", 12: "exits:\n east: {room: 13}\n", 13: "name: thirteen\n", 14: "name: fourteen\n", } issues := runGridCheck(t, rooms, 1) if !containsMsg(issues, "Grid overlap") { t.Errorf("expected a 3D grid overlap, got: %+v", issues) } } func TestGrid3DCleanViaUpDown(t *testing.T) { // Clean 3D layout: up/down maintain same (x,y) across z-levels. // 1(0,0,0) up→2(0,0,1) east→3(1,0,1) down→4(1,0,0) south→5(1,1,0) rooms := map[int]string{ 1: "exits:\n up: {room: 2}\n", 2: "exits:\n east: {room: 3}\n down: {room: 1}\n", 3: "exits:\n down: {room: 4}\n west: {room: 2}\n", 4: "exits:\n south: {room: 5}\n up: {room: 3}\n", 5: "name: five\n exits:\n north: {room: 4}\n", } if issues := runGridCheck(t, rooms, 1); len(issues) != 0 { t.Errorf("expected no grid issues, got: %+v", issues) } } func TestGrid3DOverlapUpDown(t *testing.T) { // Two rooms land on the same 3D cell via up/down paths. // 1(0,0,0) up→2(0,0,1). // 1(0,0,0) east→3(1,0,0) up→4(1,0,1) west→5 wants (0,0,1) — already room 2. rooms := map[int]string{ 1: "exits:\n up: {room: 2}\n east: {room: 3}\n", 2: "name: two\n", 3: "exits:\n up: {room: 4}\n west: {room: 1}\n", 4: "exits:\n west: {room: 5}\n down: {room: 3}\n", 5: "name: five\n", } issues := runGridCheck(t, rooms, 1) if !containsMsg(issues, "Grid overlap") { t.Errorf("expected a 3D grid overlap, got: %+v", issues) } } func TestGrid3DTwistUpDown(t *testing.T) { // Room 4 forced to two different coordinates via up/down paths. // path1: 1 up→2(0,0,1) south→3(0,1,1) east→4 places 4 at (1,1,1) // path2: 1 east→A(1,0,0) up→B(1,0,1) east→C(2,0,1) south→4 wants (2,1,1) // 4 already at (1,1,1) from path1 → twist. rooms := map[int]string{ 1: "exits:\n up: {room: 2}\n east: {room: 6}\n", 2: "exits:\n south: {room: 3}\n down: {room: 1}\n", 3: "exits:\n east: {room: 4}\n north: {room: 2}\n", 4: "name: four\n", 6: "exits:\n up: {room: 7}\n west: {room: 1}\n", 7: "exits:\n east: {room: 8}\n down: {room: 6}\n", 8: "exits:\n south: {room: 4}\n west: {room: 7}\n", } issues := runGridCheck(t, rooms, 1) if !containsMsg(issues, "Grid twist") { t.Errorf("expected a 3D grid twist, got: %+v", issues) } } func TestGridDiagonalClean(t *testing.T) { // Room 1 -> NE -> Room 2 (at 1,-1). Clean diagonal placement. rooms := map[int]string{ 1: "exits:\n northeast: {room: 2}\n", 2: "name: two\n exits:\n southwest: {room: 1}\n", } if issues := runGridCheck(t, rooms, 1); len(issues) != 0 { t.Errorf("expected no grid issues, got: %+v", issues) } } func TestGridDiagonalOverlap(t *testing.T) { // Two different rooms both resolve to (2,1): // 1→SE→2→E→4 lands 4 at (2,1). // 1→E→3→SE→5 lands 5 at (2,1). Room 4 != 5 → overlap. rooms := map[int]string{ 1: "exits:\n southeast: {room: 2}\n east: {room: 3}\n", 2: "exits:\n east: {room: 4}\n", 3: "exits:\n southeast: {room: 5}\n", 4: "name: four\n", 5: "name: five\n", } issues := runGridCheck(t, rooms, 1) if !containsMsg(issues, "Grid overlap") { t.Errorf("expected a grid overlap via diagonal, got: %+v", issues) } } func TestGridDiagonalTwist(t *testing.T) { // Room 4 forced onto two different cells: // path1: 1→E→2 (2 at 1,0). 2→NE→4 places 4 at (2,-1). // path2: 1→N→3 (3 at 0,-1). 3→E→5 (5 at 1,-1). 5→N→4 wants 4 at (1,-2). // 4 already placed at (2,-1) but wanted at (1,-2) → twist. rooms := map[int]string{ 1: "exits:\n east: {room: 2}\n north: {room: 3}\n", 2: "exits:\n northeast: {room: 4}\n", 3: "exits:\n east: {room: 5}\n", 4: "name: four\n", 5: "exits:\n north: {room: 4}\n", } issues := runGridCheck(t, rooms, 1) if !containsMsg(issues, "Grid twist") { t.Errorf("expected a grid twist via diagonal, got: %+v", issues) } }