package world import ( "os" "path/filepath" "strconv" "testing" ) func writeGridTestRoom(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 loadGridRoom(w *World) func(int) (*Room, bool) { return func(id int) (*Room, bool) { r, err := w.LoadRoom(id) if err != nil { return nil, false } return r, true } } func hasConflictKind(conflicts []GridConflict, kind string) bool { for _, c := range conflicts { if c.Kind == kind { return true } } return false } func TestBuildGridConflictsClean(t *testing.T) { dir := t.TempDir() writeGridTestRoom(t, dir, 1, "exits:\n south: {room: 3}\n east: {room: 2}\n") writeGridTestRoom(t, dir, 2, "exits:\n south: {room: 4}\n") writeGridTestRoom(t, dir, 3, "exits:\n east: {room: 4}\n") writeGridTestRoom(t, dir, 4, "name: corner\n") w := New(dir) if conflicts := BuildGridConflicts(1, loadGridRoom(w)); len(conflicts) != 0 { t.Errorf("expected no conflicts, got: %+v", conflicts) } } func TestBuildGridConflictsOverlap(t *testing.T) { dir := t.TempDir() writeGridTestRoom(t, dir, 1, "exits:\n south: {room: 3}\n east: {room: 2}\n") writeGridTestRoom(t, dir, 2, "exits:\n south: {room: 5}\n") writeGridTestRoom(t, dir, 3, "exits:\n east: {room: 4}\n") writeGridTestRoom(t, dir, 4, "name: four\n") writeGridTestRoom(t, dir, 5, "name: five\n") w := New(dir) conflicts := BuildGridConflicts(1, loadGridRoom(w)) if !hasConflictKind(conflicts, "overlap") { t.Errorf("expected an overlap conflict, got: %+v", conflicts) } } func TestBuildGridConflictsTwist(t *testing.T) { dir := t.TempDir() writeGridTestRoom(t, dir, 1, "exits:\n south: {room: 4}\n east: {room: 2}\n") writeGridTestRoom(t, dir, 2, "exits:\n east: {room: 3}\n") writeGridTestRoom(t, dir, 4, "exits:\n east: {room: 3}\n") writeGridTestRoom(t, dir, 3, "name: three\n") w := New(dir) conflicts := BuildGridConflicts(1, loadGridRoom(w)) if !hasConflictKind(conflicts, "twist") { t.Errorf("expected a twist conflict, got: %+v", conflicts) } } func TestBuildGridConflictsDiagonalClean(t *testing.T) { dir := t.TempDir() writeGridTestRoom(t, dir, 1, "exits:\n northeast: {room: 2}\n") writeGridTestRoom(t, dir, 2, "name: two\n exits:\n southwest: {room: 1}\n") w := New(dir) if conflicts := BuildGridConflicts(1, loadGridRoom(w)); len(conflicts) != 0 { t.Errorf("expected no conflicts for clean diagonal, got: %+v", conflicts) } } // TestBuildGridConflictsHypotheticalEdit models the insert-remove use case: // the load closure returns edited copies of rooms reflecting a proposed edit, // and BuildGridConflicts reports whether the resulting world is still clean. // Here we model "insert a new room 5 between 1 and 2 on the east axis" and // confirm the (clean) post-edit world has no conflicts. func TestBuildGridConflictsHypotheticalInsert(t *testing.T) { dir := t.TempDir() writeGridTestRoom(t, dir, 1, "name: one\nexits:\n east: {room: 2}\n") writeGridTestRoom(t, dir, 2, "name: two\nexits:\n west: {room: 1}\n") w := New(dir) newRoom := &Room{Name: "new", Exits: map[ExitDir]ExitDef{ East: {Room: 2}, West: {Room: 1}, }} load := func(id int) (*Room, bool) { if id == 5 { return newRoom, true } r, err := w.LoadRoom(id) if err != nil { return nil, false } copy := *r if r.Exits != nil { copy.Exits = make(map[ExitDir]ExitDef, len(r.Exits)) for k, v := range r.Exits { if id == 1 && k == East { copy.Exits[k] = ExitDef{Room: 5, Condition: v.Condition, BlockedMessage: v.BlockedMessage, OnTraverse: v.OnTraverse, Hidden: v.Hidden, AlwaysBlocked: v.AlwaysBlocked} continue } if id == 2 && k == West { copy.Exits[k] = ExitDef{Room: 5, Condition: v.Condition, BlockedMessage: v.BlockedMessage, OnTraverse: v.OnTraverse, Hidden: v.Hidden, AlwaysBlocked: v.AlwaysBlocked} continue } copy.Exits[k] = v } } return ©, true } if conflicts := BuildGridConflicts(1, load); len(conflicts) != 0 { t.Errorf("clean insert should produce no conflicts, got: %+v", conflicts) } } // TestBuildGridConflictsHypotheticalRemoveOverlap models "remove room 2 and // pull the beyond-rooms back toward room 1". Layout: // // 1 east→2 east→3 south→6 (6 at (2,1,0)) // 1 south→4 east→5 (5 at (1,1,0)) // // After removing 2 and rewiring 1.Exits[East]=3, 3.Exits[West]=1, the BFS // re-places 3 at (1,0,0) and 6 at (1,1,0) — colliding with 5 which stays at // (1,1,0). The helper must report an overlap without anything being written. func TestBuildGridConflictsHypotheticalRemoveOverlap(t *testing.T) { dir := t.TempDir() writeGridTestRoom(t, dir, 1, "exits:\n east: {room: 2}\n south: {room: 4}\n") writeGridTestRoom(t, dir, 2, "exits:\n east: {room: 3}\n west: {room: 1}\n") writeGridTestRoom(t, dir, 3, "exits:\n south: {room: 6}\n west: {room: 2}\n") writeGridTestRoom(t, dir, 4, "exits:\n east: {room: 5}\n north: {room: 1}\n") writeGridTestRoom(t, dir, 5, "exits:\n west: {room: 4}\n") writeGridTestRoom(t, dir, 6, "exits:\n north: {room: 3}\n") w := New(dir) load := func(id int) (*Room, bool) { if id == 2 { return nil, false // room removed } r, err := w.LoadRoom(id) if err != nil { return nil, false } rc := *r rc.Exits = make(map[ExitDir]ExitDef, len(r.Exits)) for k, v := range r.Exits { rc.Exits[k] = v } if id == 1 { rc.Exits[East] = ExitDef{Room: 3} } if id == 3 { rc.Exits[West] = ExitDef{Room: 1} } return &rc, true } conflicts := BuildGridConflicts(1, load) if !hasConflictKind(conflicts, "overlap") { t.Errorf("expected an overlap from pulling 6 onto 5's cell, got: %+v", conflicts) } }