From 4d25743ee52b987a0f1354b954e999d6bd4acc76 Mon Sep 17 00:00:00 2001 From: Karma Riuk Date: Fri, 4 Aug 2023 21:05:28 +0200 Subject: [PATCH] Written the first version of a text reader, with tests --- assets/normal.txt | 11 ++++ assets/trivial.txt | 3 + io/reader/text.go | 125 +++++++++++++++++++++++++++++++++++++++++ io/reader/text_test.go | 81 ++++++++++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 assets/normal.txt create mode 100644 assets/trivial.txt create mode 100644 io/reader/text.go create mode 100644 io/reader/text_test.go diff --git a/assets/normal.txt b/assets/normal.txt new file mode 100644 index 0000000..ad20fa0 --- /dev/null +++ b/assets/normal.txt @@ -0,0 +1,11 @@ +##### ##### +# # # +##### ### # +# # # +# # ##### # +# # # +### ### # # +# # # # +# ####### # +# # # +##### ##### diff --git a/assets/trivial.txt b/assets/trivial.txt new file mode 100644 index 0000000..2820989 --- /dev/null +++ b/assets/trivial.txt @@ -0,0 +1,3 @@ +## ## +# # +### # diff --git a/io/reader/text.go b/io/reader/text.go new file mode 100644 index 0000000..042ba1d --- /dev/null +++ b/io/reader/text.go @@ -0,0 +1,125 @@ +package reader + +import ( + "bufio" + "fmt" + "maze-solver/maze" + "os" +) + +type TextReader struct { + PathChar, WallChar byte +} + +func (r *TextReader) Read(filename string) (*maze.Maze, error) { + nodesByCoord := make(map[maze.Coordinates]*maze.Node) + var lines []string + + ret := &maze.Maze{} + + if _, err := os.Stat(filename); err != nil { + return nil, err + } + + file, err := os.Open(filename) + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + + y := 0 + var line string + for scanner.Scan() { + line = scanner.Text() + + if len(lines) == 0 { + lines = make([]string, 0, len(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) + ret.Nodes = append(ret.Nodes, node) + nodesByCoord[coords] = node + + r.lookupNeighbourAbove(&lines, node, &nodesByCoord) + 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) + } + } + } + lines = append(lines, line) + y++ + } + y-- + // Parse last line to get exit + for x, rune := range line { + char := byte(rune) + if char == r.PathChar { + fmt.Printf("last line number: %v\n", y) + coords := maze.Coordinates{X: x, Y: y} + node := maze.NewNode(coords) + r.lookupNeighbourAbove(&lines, node, &nodesByCoord) + 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) { + for y := node.Coords.Y - 1; y >= 0; y-- { + if (*lines)[y][node.Coords.X] == r.WallChar { + break + } + + neighbour, ok := (*nodesByCoord)[maze.Coordinates{X: node.Coords.X, Y: y}] + if ok { + node.Up = neighbour + neighbour.Down = node + } + } +} + +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 { + 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 + fmt.Printf("Setting left of %v to %v\n", node.Coords, neighbour.Coords) + neighbour.Right = node + break + } + } +} diff --git a/io/reader/text_test.go b/io/reader/text_test.go new file mode 100644 index 0000000..7f031fc --- /dev/null +++ b/io/reader/text_test.go @@ -0,0 +1,81 @@ +package reader + +import ( + "fmt" + "maze-solver/maze" + "maze-solver/utils" + "testing" +) + +func TestTextRead(t *testing.T) { + /* trivial.txt + ## ## + # # + ### # + + Nodes are + ##0## + #123# + ###4# + */ + nodes := make([]*maze.Node, 5) + + nodes[0] = maze.NewNode(maze.Coordinates{X: 2, Y: 0}) + + nodes[1] = maze.NewNode(maze.Coordinates{X: 1, Y: 1}) + nodes[2] = maze.NewNode(maze.Coordinates{X: 2, Y: 1}) + nodes[3] = maze.NewNode(maze.Coordinates{X: 3, Y: 1}) + + nodes[4] = maze.NewNode(maze.Coordinates{X: 3, Y: 2}) + + nodes[0].Down = nodes[2] + + nodes[1].Right = nodes[2] + + nodes[2].Up = nodes[0] + nodes[2].Left = nodes[1] + nodes[2].Right = nodes[3] + + nodes[3].Left = nodes[2] + nodes[3].Down = nodes[4] + + nodes[4].Up = nodes[3] + + reader := TextReader{ + PathChar: ' ', + WallChar: '#', + } + + filename := "../../assets/trivial.txt" + got, err := reader.Read(filename) + utils.Check(err, "Couldn't create maze from %q", 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)) + } + + for i, got := range got.Nodes { + fmt.Println(i) + 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 + } + + if got == nil { + t.Fatalf("No %s node of %v, want %v", side, i, expected.Coords) + } + + if got.Coords != expected.Coords { + t.Fatalf("Coords %s node of %v: %v, but want %v", side, i, got.Coords, expected.Coords) + } +}