diff --git a/io/reader/reader.go b/io/reader/reader.go index 9116d6e..304d842 100644 --- a/io/reader/reader.go +++ b/io/reader/reader.go @@ -3,5 +3,5 @@ package reader import "maze-solver/maze" type Reader interface { - Read(filename string) (*maze.Maze, error) + Read() (*maze.RawMaze, error) } diff --git a/io/reader/text.go b/io/reader/text.go index 5355b8b..24b28bb 100644 --- a/io/reader/text.go +++ b/io/reader/text.go @@ -2,26 +2,23 @@ package reader import ( "bufio" - "fmt" "maze-solver/maze" "os" ) type TextReader struct { + Filename string PathChar, WallChar byte } -func (r *TextReader) Read(filename string) (*maze.Maze, error) { - nodesByCoord := make(map[maze.Coordinates]*maze.Node) +func (r TextReader) Read() (*maze.RawMaze, error) { var lines []string - ret := &maze.Maze{} - - if _, err := os.Stat(filename); err != nil { + if _, err := os.Stat(r.Filename); err != nil { return nil, err } - file, err := os.Open(filename) + file, err := os.Open(r.Filename) if err != nil { return nil, err } @@ -37,120 +34,5 @@ func (r *TextReader) Read(filename string) (*maze.Maze, error) { file.Close() } - for y, line := range lines { - fmt.Println(line) - for x := 1; x < len(line)-1; x++ { - char := line[x] - var left_char, right_char, above_char byte - - if y > 0 { - left_char = line[x-1] - right_char = line[x+1] - above_char = lines[y-1][x] - } - - // Parse first line to get entrance - if y == 0 && char == r.PathChar { - coords := maze.Coordinates{X: x, Y: y} - node := maze.NewNode(coords) - ret.Nodes = append(ret.Nodes, node) - nodesByCoord[coords] = node - continue - } - - // Parse middle of the maze - if y > 0 && char == r.PathChar && - (left_char == r.WallChar && right_char == r.PathChar || - left_char == r.PathChar && right_char == r.WallChar || - above_char == r.PathChar && (left_char == r.PathChar || right_char == r.PathChar)) { - coords := maze.Coordinates{X: x, Y: y} - node := maze.NewNode(coords) - - r.lookupNeighbourAbove(&lines, node, &nodesByCoord, ret) - - ret.Nodes = append(ret.Nodes, node) - nodesByCoord[coords] = node - - if left_char == r.PathChar && right_char == r.WallChar || - above_char == r.PathChar && (left_char == r.PathChar || right_char == r.PathChar) { - r.lookupNeighbourLeft(&line, node, &nodesByCoord) - } - } - } - } - - fmt.Println(len(lines)) - // Parse last line to get exit - for x, rune := range lines[len(lines)-1] { - char := byte(rune) - if char == r.PathChar { - coords := maze.Coordinates{X: x, Y: len(lines) - 1} - node := maze.NewNode(coords) - r.lookupNeighbourAbove(&lines, node, &nodesByCoord, ret) - ret.Nodes = append(ret.Nodes, node) - break - } - } - - return ret, nil -} - -func (r *TextReader) lookupNeighbourAbove(lines *[]string, node *maze.Node, nodesByCoord *map[maze.Coordinates]*maze.Node, m *maze.Maze) { - for y := node.Coords.Y - 1; y >= 0; y-- { - neighbour, ok := (*nodesByCoord)[maze.Coordinates{X: node.Coords.X, Y: y}] - - if ok { - node.Up = neighbour - neighbour.Down = node - break - } - - if y > 0 && (*lines)[y][node.Coords.X] == r.WallChar { - y++ - if y == node.Coords.Y { - break - } - coords := maze.Coordinates{X: node.Coords.X, Y: y} - new_node := maze.NewNode(coords) - r.lookupNeighbourLeft(&(*lines)[y], new_node, nodesByCoord) - r.lookupNeighbourRight(&(*lines)[y], new_node, nodesByCoord) - (*nodesByCoord)[coords] = new_node - m.Nodes = append(m.Nodes, new_node) - - node.Up = new_node - new_node.Down = node - break - } - - } -} - -func (r *TextReader) lookupNeighbourLeft(line *string, node *maze.Node, nodesByCoord *map[maze.Coordinates]*maze.Node) { - for x := node.Coords.X - 1; x > 0; x-- { - if (*line)[x] == r.WallChar && x < node.Coords.X-1 { - panic(fmt.Sprintf("Found no node before wall while looking to the left at neighbours of node %v", node)) - } - - neighbour, ok := (*nodesByCoord)[maze.Coordinates{X: x, Y: node.Coords.Y}] - if ok { - node.Left = neighbour - neighbour.Right = node - break - } - } -} - -func (r *TextReader) lookupNeighbourRight(line *string, node *maze.Node, nodesByCoord *map[maze.Coordinates]*maze.Node) { - for x := node.Coords.X + 1; x < len(*line); x++ { - if (*line)[x] == r.WallChar { - panic(fmt.Sprintf("Found no node before wall while looking to the right at neighbours of node %v", node)) - } - - neighbour, ok := (*nodesByCoord)[maze.Coordinates{X: x, Y: node.Coords.Y}] - if ok { - node.Right = neighbour - neighbour.Left = node - break - } - } + return &maze.RawMaze{PathChar: r.PathChar, WallChar: r.WallChar, Data: lines}, nil } diff --git a/maze/maze.go b/maze/maze.go index 471e361..c95c00e 100644 --- a/maze/maze.go +++ b/maze/maze.go @@ -19,6 +19,11 @@ func NewNode(coords Coordinates) *Node { } } +type RawMaze struct { + PathChar, WallChar byte + Data []string +} + type Maze struct { Width, Height uint Nodes []*Node diff --git a/maze/parser/parser.go b/maze/parser/parser.go new file mode 100644 index 0000000..d445b12 --- /dev/null +++ b/maze/parser/parser.go @@ -0,0 +1,138 @@ +package parser + +import ( + "fmt" + "maze-solver/io/reader" + "maze-solver/maze" +) + +const ( + WallChar = '#' + PathChar = ' ' +) + +func Parse(reader reader.Reader) (*maze.Maze, error) { + nodesByCoord := make(map[maze.Coordinates]*maze.Node) + ret := &maze.Maze{} + + raw_maze, err := reader.Read() + if err != nil { + return nil, err + } + + for y, line := range raw_maze.Data { + fmt.Println(line) + for x := 1; x < len(line)-1; x++ { + char := line[x] + var left_char, right_char, above_char byte + + if y > 0 { + left_char = line[x-1] + right_char = line[x+1] + above_char = raw_maze.Data[y-1][x] + } + + // Parse first line to get entrance + if y == 0 && char == PathChar { + coords := maze.Coordinates{X: x, Y: y} + node := maze.NewNode(coords) + ret.Nodes = append(ret.Nodes, node) + nodesByCoord[coords] = node + continue + } + + // Parse middle of the maze + if y > 0 && char == PathChar && + (left_char == WallChar && right_char == PathChar || + left_char == PathChar && right_char == WallChar || + above_char == PathChar && (left_char == PathChar || right_char == PathChar)) { + coords := maze.Coordinates{X: x, Y: y} + node := maze.NewNode(coords) + + lookupNeighbourAbove(&raw_maze.Data, node, &nodesByCoord, ret) + + ret.Nodes = append(ret.Nodes, node) + nodesByCoord[coords] = node + + if left_char == PathChar && right_char == WallChar || + above_char == PathChar && (left_char == PathChar || right_char == PathChar) { + lookupNeighbourLeft(&line, node, &nodesByCoord) + } + } + } + } + + // Parse last line to get exit + for x, rune := range raw_maze.Data[len(raw_maze.Data)-1] { + char := byte(rune) + if char == PathChar { + coords := maze.Coordinates{X: x, Y: len(raw_maze.Data) - 1} + node := maze.NewNode(coords) + lookupNeighbourAbove(&raw_maze.Data, node, &nodesByCoord, ret) + ret.Nodes = append(ret.Nodes, node) + break + } + } + + return ret, nil +} + +func lookupNeighbourAbove(Data *[]string, node *maze.Node, nodesByCoord *map[maze.Coordinates]*maze.Node, m *maze.Maze) { + for y := node.Coords.Y - 1; y >= 0; y-- { + neighbour, ok := (*nodesByCoord)[maze.Coordinates{X: node.Coords.X, Y: y}] + + if ok { + node.Up = neighbour + neighbour.Down = node + break + } + + if y > 0 && (*Data)[y][node.Coords.X] == WallChar { + y++ + if y == node.Coords.Y { + break + } + coords := maze.Coordinates{X: node.Coords.X, Y: y} + new_node := maze.NewNode(coords) + lookupNeighbourLeft(&(*Data)[y], new_node, nodesByCoord) + lookupNeighbourRight(&(*Data)[y], new_node, nodesByCoord) + (*nodesByCoord)[coords] = new_node + m.Nodes = append(m.Nodes, new_node) + + node.Up = new_node + new_node.Down = node + break + } + + } +} + +func lookupNeighbourLeft(line *string, node *maze.Node, nodesByCoord *map[maze.Coordinates]*maze.Node) { + for x := node.Coords.X - 1; x > 0; x-- { + if (*line)[x] == WallChar && x < node.Coords.X-1 { + panic(fmt.Sprintf("Found no node before wall while looking to the left at neighbours of node %v", node)) + } + + neighbour, ok := (*nodesByCoord)[maze.Coordinates{X: x, Y: node.Coords.Y}] + if ok { + node.Left = neighbour + neighbour.Right = node + break + } + } +} + +func lookupNeighbourRight(line *string, node *maze.Node, nodesByCoord *map[maze.Coordinates]*maze.Node) { + for x := node.Coords.X + 1; x < len(*line); x++ { + if (*line)[x] == WallChar { + panic(fmt.Sprintf("Found no node before wall while looking to the right at neighbours of node %v", node)) + } + + neighbour, ok := (*nodesByCoord)[maze.Coordinates{X: x, Y: node.Coords.Y}] + if ok { + node.Right = neighbour + neighbour.Left = node + break + } + } +} diff --git a/io/reader/text_test.go b/maze/parser/parser_test.go similarity index 52% rename from io/reader/text_test.go rename to maze/parser/parser_test.go index 8386db7..1ae878d 100644 --- a/io/reader/text_test.go +++ b/maze/parser/parser_test.go @@ -1,13 +1,15 @@ -package reader +package parser import ( "fmt" + "maze-solver/io/reader" "maze-solver/maze" "maze-solver/utils" "testing" ) func TestTextReadTrivial(t *testing.T) { + fmt.Println("---------- Trivial ----------") /* trivial.txt ## ## # # @@ -41,14 +43,14 @@ func TestTextReadTrivial(t *testing.T) { nodes[4].Up = nodes[3] - reader := TextReader{ + reader := reader.TextReader{ + Filename: "../../assets/trivial.txt", PathChar: ' ', WallChar: '#', } - filename := "../../assets/trivial.txt" - got, err := reader.Read(filename) - utils.Check(err, "Couldn't create maze from %q", filename) + got, err := Parse(reader) + utils.Check(err, "Couldn't create maze from %q", reader.Filename) if len(nodes) != len(got.Nodes) { t.Fatalf("Didn't get the same size of nodes: %v, want %v", len(got.Nodes), len(nodes)) @@ -66,6 +68,7 @@ func TestTextReadTrivial(t *testing.T) { } func TestTextReadTrivialBigger(t *testing.T) { + fmt.Println("---------- Trivial Bigger ----------") /* trivial-bigger.txt ### ### ### ### @@ -103,14 +106,14 @@ func TestTextReadTrivialBigger(t *testing.T) { nodes[4].Up = nodes[3] - reader := TextReader{ + reader := reader.TextReader{ + Filename: "../../assets/trivial-bigger.txt", PathChar: ' ', WallChar: '#', } - filename := "../../assets/trivial-bigger.txt" - got, err := reader.Read(filename) - utils.Check(err, "Couldn't create maze from %q", filename) + got, err := Parse(reader) + utils.Check(err, "Couldn't create maze from %q", reader.Filename) if len(nodes) != len(got.Nodes) { t.Fatalf("Didn't get the same size of nodes: %v, want %v", len(got.Nodes), len(nodes)) @@ -132,6 +135,7 @@ func TestTextReadTrivialBigger(t *testing.T) { } func TestTextReadTrivialBiggerStaggered(t *testing.T) { + fmt.Println("---------- Trivial Staggered ----------") /* trivial-bigger-staggered.txt ### ### ### ### @@ -146,7 +150,6 @@ func TestTextReadTrivialBiggerStaggered(t *testing.T) { #### ## ####5## */ - fmt.Println("---------- STAGGERED ----------") nodes := make([]*maze.Node, 6) nodes[0] = maze.NewNode(maze.Coordinates{X: 3, Y: 0}) @@ -175,14 +178,14 @@ func TestTextReadTrivialBiggerStaggered(t *testing.T) { nodes[5].Up = nodes[4] - reader := TextReader{ + reader := reader.TextReader{ + Filename: "../../assets/trivial-bigger-staggered.txt", PathChar: ' ', WallChar: '#', } - filename := "../../assets/trivial-bigger-staggered.txt" - got, err := reader.Read(filename) - utils.Check(err, "Couldn't create maze from %q", filename) + got, err := Parse(reader) + utils.Check(err, "Couldn't create maze from %q", reader.Filename) if len(nodes) != len(got.Nodes) { t.Fatalf("Didn't get the same size of nodes: %v, want %v", len(got.Nodes), len(nodes)) @@ -203,6 +206,169 @@ func TestTextReadTrivialBiggerStaggered(t *testing.T) { } } +func TestTextReadNormal(t *testing.T) { + return + fmt.Println("---------- Normal ----------") + /* trivial-bigger-staggered.txt + ##### ##### + # # # + ##### ### # + # # # + # # ##### # + # # # + ### ### # # + # # # # + # ####### # + # # # + ##### ##### + + Nodes are + #####0##### + #1 2#3 4# + ##### ### # + #5 6#7 8# + # # ##### # + #9#A G B# + ### ### # # + #C D#E F# # + # ####### # + #H I#J K# + #####L##### + */ + // TODO: we are not detecting vertical dead-ends that go downwards + nodes := make([]*maze.Node, 22) + + // ---- Node creation ---- + coords := []struct{ x, y int }{ + {5, 0}, // 0 + + {1, 1}, // 1 + {5, 1}, // 2 + {7, 1}, // 3 + {9, 1}, // 4 + + {1, 3}, // 5 + {3, 3}, // 6 + {5, 3}, // 7 + {9, 3}, // 8 + + {1, 5}, // 9 + {3, 5}, // A (10) + {7, 5}, // G (16) + {9, 5}, // B (11) + + {1, 7}, // C (12) + {3, 7}, // D (13) + {5, 7}, // E (14) + {7, 7}, // F (15) + + {1, 9}, // H (17) + {5, 9}, // I (18) + {7, 9}, // J (19) + {9, 9}, // K (20) + + {5, 10}, // L (21) + } + + for i, coord := range coords { + nodes[i] = maze.NewNode(maze.Coordinates{X: coord.x, Y: coord.y}) + } + + // ---- Node linking ---- + nodes[0].Down = nodes[2] + + nodes[1].Right = nodes[2] + + nodes[2].Up = nodes[0] + nodes[2].Left = nodes[1] + nodes[2].Down = nodes[7] + + nodes[3].Right = nodes[4] + + nodes[4].Left = nodes[3] + nodes[4].Down = nodes[8] + + nodes[5].Right = nodes[6] + nodes[5].Down = nodes[9] + + nodes[6].Left = nodes[5] + nodes[6].Down = nodes[10] + + nodes[7].Right = nodes[8] + + nodes[8].Up = nodes[4] + nodes[8].Left = nodes[7] + nodes[8].Down = nodes[11] + + nodes[9].Up = nodes[5] + + nodes[10].Up = nodes[6] + nodes[10].Right = nodes[16] + nodes[10].Down = nodes[13] + + nodes[11].Up = nodes[6] + nodes[11].Right = nodes[16] + nodes[11].Down = nodes[20] + + nodes[12].Right = nodes[13] + nodes[12].Down = nodes[17] + + nodes[13].Up = nodes[10] + nodes[13].Left = nodes[16] + + nodes[14].Right = nodes[15] + + nodes[15].Up = nodes[16] + nodes[15].Left = nodes[14] + + nodes[16].Left = nodes[10] + nodes[16].Right = nodes[11] + nodes[16].Down = nodes[15] + + nodes[17].Up = nodes[12] + nodes[17].Right = nodes[18] + + nodes[18].Left = nodes[17] + nodes[18].Down = nodes[21] + + nodes[19].Right = nodes[20] + + nodes[20].Up = nodes[11] + nodes[20].Left = nodes[19] + + nodes[21].Up = nodes[20] + + reader := reader.TextReader{ + Filename: "../../assets/normal.txt", + PathChar: ' ', + WallChar: '#', + } + + got, err := Parse(reader) + utils.Check(err, "Couldn't create maze from %q", reader.Filename) + + if len(nodes) != len(got.Nodes) { + for i, node := range got.Nodes { + fmt.Printf("%v: %v\n", i, node) + } + t.Fatalf("Didn't get the same size of nodes: %v, want %v", len(got.Nodes), len(nodes)) + } + + for _, node := range got.Nodes { + fmt.Println(node) + } + + for i, got := range got.Nodes { + expected := nodes[i] + + checkNode(t, i, got, expected, "") + checkNode(t, i, got.Left, expected.Left, "left") + checkNode(t, i, got.Right, expected.Right, "Right") + checkNode(t, i, got.Up, expected.Up, "Up") + checkNode(t, i, got.Down, expected.Down, "Down") + } +} + func checkNode(t *testing.T, i int, got *maze.Node, expected *maze.Node, side string) { if expected == nil { return