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:
parent
e04aad4b77
commit
ab6f85b7b6
@ -3,5 +3,5 @@ package reader
|
||||
import "maze-solver/maze"
|
||||
|
||||
type Reader interface {
|
||||
Read(filename string) (*maze.Maze, error)
|
||||
Read() (*maze.RawMaze, error)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
138
maze/parser/parser.go
Normal file
138
maze/parser/parser.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
Loading…
Reference in New Issue
Block a user