Reader -> Reader+Parser refacfotring: create the

parser package, moved the parsing aspect of reader
to parser (still have some naughty stuff like
WallChar and PathChar in parser but it'll be fixed
in next commit)
This commit is contained in:
Karma Riuk 2023-08-05 10:38:46 +02:00
parent cfa683dc83
commit fcb2bc0d51
5 changed files with 329 additions and 138 deletions

View File

@ -3,5 +3,5 @@ package reader
import "maze-solver/maze" import "maze-solver/maze"
type Reader interface { type Reader interface {
Read(filename string) (*maze.Maze, error) Read() (*maze.RawMaze, error)
} }

View File

@ -2,26 +2,23 @@ package reader
import ( import (
"bufio" "bufio"
"fmt"
"maze-solver/maze" "maze-solver/maze"
"os" "os"
) )
type TextReader struct { type TextReader struct {
Filename string
PathChar, WallChar byte PathChar, WallChar byte
} }
func (r *TextReader) Read(filename string) (*maze.Maze, error) { func (r TextReader) Read() (*maze.RawMaze, error) {
nodesByCoord := make(map[maze.Coordinates]*maze.Node)
var lines []string var lines []string
ret := &maze.Maze{} if _, err := os.Stat(r.Filename); err != nil {
if _, err := os.Stat(filename); err != nil {
return nil, err return nil, err
} }
file, err := os.Open(filename) file, err := os.Open(r.Filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,120 +34,5 @@ func (r *TextReader) Read(filename string) (*maze.Maze, error) {
file.Close() file.Close()
} }
for y, line := range lines { return &maze.RawMaze{PathChar: r.PathChar, WallChar: r.WallChar, Data: lines}, nil
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
}
}
} }

View File

@ -19,6 +19,11 @@ func NewNode(coords Coordinates) *Node {
} }
} }
type RawMaze struct {
PathChar, WallChar byte
Data []string
}
type Maze struct { type Maze struct {
Width, Height uint Width, Height uint
Nodes []*Node Nodes []*Node

138
maze/parser/parser.go Normal file
View File

@ -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
}
}
}

View File

@ -1,13 +1,15 @@
package reader package parser
import ( import (
"fmt" "fmt"
"maze-solver/io/reader"
"maze-solver/maze" "maze-solver/maze"
"maze-solver/utils" "maze-solver/utils"
"testing" "testing"
) )
func TestTextReadTrivial(t *testing.T) { func TestTextReadTrivial(t *testing.T) {
fmt.Println("---------- Trivial ----------")
/* trivial.txt /* trivial.txt
## ## ## ##
# # # #
@ -41,14 +43,14 @@ func TestTextReadTrivial(t *testing.T) {
nodes[4].Up = nodes[3] nodes[4].Up = nodes[3]
reader := TextReader{ reader := reader.TextReader{
Filename: "../../assets/trivial.txt",
PathChar: ' ', PathChar: ' ',
WallChar: '#', WallChar: '#',
} }
filename := "../../assets/trivial.txt" got, err := Parse(reader)
got, err := reader.Read(filename) utils.Check(err, "Couldn't create maze from %q", reader.Filename)
utils.Check(err, "Couldn't create maze from %q", filename)
if len(nodes) != len(got.Nodes) { if len(nodes) != len(got.Nodes) {
t.Fatalf("Didn't get the same size of nodes: %v, want %v", len(got.Nodes), len(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) { func TestTextReadTrivialBigger(t *testing.T) {
fmt.Println("---------- Trivial Bigger ----------")
/* trivial-bigger.txt /* trivial-bigger.txt
### ### ### ###
### ### ### ###
@ -103,14 +106,14 @@ func TestTextReadTrivialBigger(t *testing.T) {
nodes[4].Up = nodes[3] nodes[4].Up = nodes[3]
reader := TextReader{ reader := reader.TextReader{
Filename: "../../assets/trivial-bigger.txt",
PathChar: ' ', PathChar: ' ',
WallChar: '#', WallChar: '#',
} }
filename := "../../assets/trivial-bigger.txt" got, err := Parse(reader)
got, err := reader.Read(filename) utils.Check(err, "Couldn't create maze from %q", reader.Filename)
utils.Check(err, "Couldn't create maze from %q", filename)
if len(nodes) != len(got.Nodes) { if len(nodes) != len(got.Nodes) {
t.Fatalf("Didn't get the same size of nodes: %v, want %v", len(got.Nodes), len(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) { func TestTextReadTrivialBiggerStaggered(t *testing.T) {
fmt.Println("---------- Trivial Staggered ----------")
/* trivial-bigger-staggered.txt /* trivial-bigger-staggered.txt
### ### ### ###
### ### ### ###
@ -146,7 +150,6 @@ func TestTextReadTrivialBiggerStaggered(t *testing.T) {
#### ## #### ##
####5## ####5##
*/ */
fmt.Println("---------- STAGGERED ----------")
nodes := make([]*maze.Node, 6) nodes := make([]*maze.Node, 6)
nodes[0] = maze.NewNode(maze.Coordinates{X: 3, Y: 0}) nodes[0] = maze.NewNode(maze.Coordinates{X: 3, Y: 0})
@ -175,14 +178,14 @@ func TestTextReadTrivialBiggerStaggered(t *testing.T) {
nodes[5].Up = nodes[4] nodes[5].Up = nodes[4]
reader := TextReader{ reader := reader.TextReader{
Filename: "../../assets/trivial-bigger-staggered.txt",
PathChar: ' ', PathChar: ' ',
WallChar: '#', WallChar: '#',
} }
filename := "../../assets/trivial-bigger-staggered.txt" got, err := Parse(reader)
got, err := reader.Read(filename) utils.Check(err, "Couldn't create maze from %q", reader.Filename)
utils.Check(err, "Couldn't create maze from %q", filename)
if len(nodes) != len(got.Nodes) { if len(nodes) != len(got.Nodes) {
t.Fatalf("Didn't get the same size of nodes: %v, want %v", len(got.Nodes), len(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) { func checkNode(t *testing.T, i int, got *maze.Node, expected *maze.Node, side string) {
if expected == nil { if expected == nil {
return return