diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dbbbe4a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +mazes diff --git a/README.md b/README.md index b360f98..40d979c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,54 @@ -# Mazes +# Mazes Chapter 3 -Based on the book [Mazes for Programmers: Code Your Own Twisty Little Passages](https://pragprog.com/book/jbmaze/mazes-for-programmers) +Finding Solutions. We’ve learned how to populate our mazes. We can now use +Dijkstra’s Algorithm to find the shortest path. -All code are in the chapter branches +Done up to page 44, for now. + +``` +Binary Tree ++---+---+---+---+---+---+---+---+---+---+ +| 0 1 2 3 4 5 6 7 8 9 | ++---+ +---+ +---+ + + +---+ + +| 3 2 | 5 4 | 7 6 | 7 | 8 | b a | ++ + +---+---+---+ +---+---+---+ + +| 4 | 3 | a 9 8 7 | e d c b | ++---+ +---+ +---+---+ + +---+ + +| 5 4 | b a | h g f | e | d c | ++---+---+---+ + + +---+---+ + + +| e d c b | i | h | g f e | d | ++---+---+ +---+ + + +---+ + + +| f e d | k j | i | h | g f | e | ++---+---+---+ +---+ +---+---+---+ + +| o n m l | k j | i h g f | ++ +---+ +---+ +---+ +---+ + + +| p | o n | m l | k j | i h | g | ++---+ +---+---+---+ +---+---+---+ + +| q p | o n m l | k j i h | ++ + +---+---+---+---+ +---+---+ + +| r | q | p o n m l | k j i | ++---+---+---+---+---+---+---+---+---+---+ + +Sidewinder ++---+---+---+---+---+---+---+---+---+---+ +| 0 1 2 3 4 5 6 7 8 9 | ++---+ + +---+---+---+---+ +---+---+ +| 3 2 | 3 4 5 6 7 | 8 9 a | ++ +---+---+ +---+ + + + +---+ +| 4 | 7 6 5 | 8 7 | 8 | 9 | a b | ++ + +---+ +---+ +---+---+---+ + +| 5 | 8 | 7 6 7 | 8 9 a b | c | ++---+ + +---+---+ +---+ + +---+ +| a 9 | 8 9 a | 9 a | b | c d | ++ +---+ + +---+ +---+---+ +---+ +| b c | 9 | a | b a | f e d e | ++---+---+ +---+---+ + + +---+ + +| c b a b c | b | g | f | g f | ++ + + + + + +---+ +---+ + +| d | c | b | c | d | c | h g h | g | ++ + + + +---+---+---+---+---+ + +| e | d | c | d e | l k j i h | ++ + +---+ +---+ +---+ + + + +| f | e f | e f | m n | k | j | i | ++---+---+---+---+---+---+---+---+---+---+ +``` diff --git a/btree.go b/btree.go new file mode 100644 index 0000000..13db15c --- /dev/null +++ b/btree.go @@ -0,0 +1,29 @@ +package main + +import ( + "math/rand" + "time" +) + +type BinaryTree struct{} + +func (b *BinaryTree) On(g Grider) { + rand.Seed(time.Now().UnixNano()) + for c := range g.EachCell() { + neighbors := make([]*Cell, 0) + if c.North != nil { + neighbors = append(neighbors, c.North) + } + if c.East != nil { + neighbors = append(neighbors, c.East) + } + l := len(neighbors) + if l != 0 { + i := rand.Intn(l) + neighbor := neighbors[i] + if neighbor != nil { + c.Link(neighbor, true) + } + } + } +} diff --git a/cell.go b/cell.go new file mode 100644 index 0000000..3751884 --- /dev/null +++ b/cell.go @@ -0,0 +1,92 @@ +package main + +type Cell struct { + row, col int + North, South, East, West *Cell + links map[*Cell]bool +} + +func NewCell(row, col int) *Cell { + c := new(Cell) + c.row, c.col = row, col + c.links = make(map[*Cell]bool) + return c +} + +func (c Cell) Row() int { + return c.row +} + +func (c Cell) Col() int { + return c.col +} + +func (c *Cell) Link(cell *Cell, bidi bool) *Cell { + c.links[cell] = true + + if bidi { + cell.Link(c, false) + } + return c +} + +func (c *Cell) Unlink(cell *Cell, bidi bool) *Cell { + delete(c.links, cell) + if bidi { + cell.Unlink(c, false) + } + return c +} + +func (c Cell) IsLinked(cell *Cell) bool { + for x := range c.links { + if x == cell { + return true + } + } + return false +} + +func (c Cell) Links() []*Cell { + list := make([]*Cell, 0) + for x := range c.links { + list = append(list, x) + } + return list +} + +func (c Cell) Neighbors() []*Cell { + list := make([]*Cell, 0) + if c.North != nil { + list = append(list, c.North) + } + if c.South != nil { + list = append(list, c.South) + } + if c.East != nil { + list = append(list, c.East) + } + if c.West != nil { + list = append(list, c.West) + } + return list +} + +func (c *Cell) Distances() *Distances { + d := NewDistances(c) + frontier := []*Cell{c} + for len(frontier) != 0 { // frontier.any? + newFrontier := make([]*Cell, 0) + for _, cell := range frontier { + for _, link := range cell.Links() { + if link == c || d.Distance(link) > 0 { + continue + } + d.SetDistance(link, d.Distance(cell)+1) + newFrontier = append(newFrontier, link) + } + } + frontier = newFrontier + } + return d +} diff --git a/distancegrid.go b/distancegrid.go new file mode 100644 index 0000000..3709b40 --- /dev/null +++ b/distancegrid.go @@ -0,0 +1,64 @@ +package main + +import "strconv" + +type DistanceGrid struct { + Grid + distances *Distances +} + +func NewDistanceGrid(rows, cols int) *DistanceGrid { + g := new(DistanceGrid) + g.distances = new(Distances) + g.Rows, g.Cols = rows, cols + g.prepareGrid() + g.configureCells() + return g +} + +func (d *DistanceGrid) SetDistances(distances *Distances) { + d.distances = distances +} + +func (g *DistanceGrid) ContentsOf(cell *Cell) string { + return strconv.FormatInt(int64(g.distances.Distance(cell)), 36) +} + +func (g *DistanceGrid) String() string { + output := "+" + for i := 0; i < g.Cols; i++ { + output += "---+" + } + output += "\n" + + for r := range g.EachRow() { + top := "|" + bottom := "+" + for _, c := range r { + body := " " + g.ContentsOf(c) + " " + eastBoundary := "" + if c.IsLinked(c.East) { + eastBoundary = " " + } else { + eastBoundary = "|" + } + top += body + top += eastBoundary + + southBoundary := "" + if c.IsLinked(c.South) { + southBoundary = " " + } else { + southBoundary = "---" + } + + corner := "+" + bottom += southBoundary + bottom += corner + } + output += top + "\n" + output += bottom + "\n" + } + + return output +} diff --git a/distances.go b/distances.go new file mode 100644 index 0000000..0156833 --- /dev/null +++ b/distances.go @@ -0,0 +1,30 @@ +package main + +type Distances struct { + root *Cell + cells map[*Cell]int +} + +func NewDistances(root *Cell) *Distances { + d := new(Distances) + d.root = root + d.cells = make(map[*Cell]int) + d.cells[root] = 0 + return d +} + +func (d *Distances) Cells() []*Cell { + cells := make([]*Cell, len(d.cells)) + for k, v := range d.cells { + cells[v] = k + } + return cells +} + +func (d *Distances) Distance(cell *Cell) int { + return d.cells[cell] +} + +func (d *Distances) SetDistance(cell *Cell, distance int) { + d.cells[cell] = distance +} diff --git a/grid.go b/grid.go new file mode 100644 index 0000000..e9e83ab --- /dev/null +++ b/grid.go @@ -0,0 +1,132 @@ +package main + +import ( + "math/rand" + "time" +) + +type Grid struct { + Rows, Cols int + grid [][]*Cell +} + +func NewGrid(rows, cols int) *Grid { + g := new(Grid) + g.Rows, g.Cols = rows, cols + g.prepareGrid() + g.configureCells() + + return g +} + +func (g *Grid) prepareGrid() { + rows := make([][]*Cell, g.Rows) + + for r := range rows { + cols := make([]*Cell, g.Cols) + for c := range cols { + cols[c] = NewCell(r, c) + } + rows[r] = cols + } + g.grid = rows +} + +func (g *Grid) configureCells() { + for r := range g.grid { + for c := range g.grid[r] { + g.grid[r][c].North = g.GetCell(r-1, c) + g.grid[r][c].South = g.GetCell(r+1, c) + g.grid[r][c].West = g.GetCell(r, c-1) + g.grid[r][c].East = g.GetCell(r, c+1) + } + } +} + +func (g *Grid) GetCell(row, col int) *Cell { + if row < 0 || row >= g.Rows { + return nil + } + if col < 0 || col >= g.Cols { + return nil + } + return g.grid[row][col] +} + +func (g *Grid) Size() int { + return g.Rows * g.Cols +} + +func (g *Grid) RandomCell() *Cell { + rand.Seed(time.Now().UnixNano()) + row := rand.Intn(g.Rows) + col := rand.Intn(g.Cols) + return g.grid[row][col] +} + +func (g *Grid) EachRow() chan []*Cell { + c := make(chan []*Cell) + go func() { + for _, r := range g.grid { + c <- r + } + close(c) + }() + return c +} + +func (g *Grid) EachCell() chan *Cell { + c := make(chan *Cell) + go func() { + for r := range g.EachRow() { + for _, i := range r { + c <- i + } + } + close(c) + }() + return c +} + +func (g *Grid) ContentsOf(cell *Cell) string { + return " " +} + +func (g *Grid) String() string { + output := "+" + for i := 0; i < g.Cols; i++ { + output += "---+" + } + output += "\n" + + for r := range g.EachRow() { + top := "|" + bottom := "+" + for _, c := range r { + body := " " + g.ContentsOf(c) + " " + eastBoundary := "" + if c.IsLinked(c.East) { + eastBoundary = " " + } else { + eastBoundary = "|" + } + top += body + top += eastBoundary + + southBoundary := "" + if c.IsLinked(c.South) { + southBoundary = " " + } else { + southBoundary = "---" + } + + corner := "+" + bottom += southBoundary + bottom += corner + } + output += top + "\n" + output += bottom + "\n" + } + + return output +} diff --git a/gridInterface.go b/gridInterface.go new file mode 100644 index 0000000..438620c --- /dev/null +++ b/gridInterface.go @@ -0,0 +1,6 @@ +package main + +type Grider interface { + EachRow() chan []*Cell + EachCell() chan *Cell +} diff --git a/imager.go b/imager.go new file mode 100644 index 0000000..cc3b69d --- /dev/null +++ b/imager.go @@ -0,0 +1,80 @@ +package main + +import ( + "image" + "image/color" + "image/draw" + "image/png" + "log" + "os" + "os/exec" +) + +var ( + white color.Color = color.RGBA{255, 255, 255, 255} + black color.Color = color.RGBA{0, 0, 0, 255} +) + +type Image struct { + size int +} + +func NewImage(size int) *Image { + return &Image{size: size} +} + +func (i *Image) DrawLine(img *image.RGBA, x1, y1, x2, y2 int, c color.Color) { + for i := x1; i < x2; i++ { + img.Set(i, y2, c) + } + for i := y1; i < y2; i++ { + img.Set(x2, i, c) + } +} + +func (i *Image) DrawGrid(g *Grid) *image.RGBA { + width := i.size * g.Cols + height := i.size * g.Rows + background := white + wall := black + + img := image.NewRGBA(image.Rect(0, 0, width+1, height+1)) + draw.Draw(img, img.Bounds(), &image.Uniform{background}, image.ZP, draw.Src) + + for c := range g.EachCell() { + x1 := c.Col() * i.size + y1 := c.Row() * i.size + x2 := (c.Col() + 1) * i.size + y2 := (c.Row() + 1) * i.size + + if c.North == nil { + i.DrawLine(img, x1, y1, x2, y1, wall) + } + if c.West == nil { + i.DrawLine(img, x1, y1, x1, y2, wall) + } + if !c.IsLinked(c.East) { + i.DrawLine(img, x2, y1, x2, y2, wall) + } + if !c.IsLinked(c.South) { + i.DrawLine(img, x1, y2, x2, y2, wall) + } + } + + return img +} + +func (i *Image) Save(filename string, img *image.RGBA) { + w, _ := os.Create(filename) + defer w.Close() + png.Encode(w, img) //Encode writes the Image m to w in PNG format. +} + +func Show(name string) { + command := "xdg-open" + cmd := exec.Command(command, name) + err := cmd.Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..c6a08fe --- /dev/null +++ b/main.go @@ -0,0 +1,24 @@ +// Not done yet, currently, what's below is just test code to see what's going on +package main + +import "fmt" + +func main() { + fmt.Println("Binary Tree") + g := NewDistanceGrid(10, 10) + b := new(BinaryTree) + b.On(g) + start := g.GetCell(0, 0) + distances := start.Distances() + g.SetDistances(distances) + fmt.Println(g) + fmt.Println("Sidewinder") + g = NewDistanceGrid(10, 10) + s := new(Sidewinder) + s.On(g) + start = g.GetCell(0, 0) + distances = start.Distances() + g.SetDistances(distances) + fmt.Println(g) + fmt.Println("Showing image") +} diff --git a/sidewinder.go b/sidewinder.go new file mode 100644 index 0000000..01c0651 --- /dev/null +++ b/sidewinder.go @@ -0,0 +1,44 @@ +package main + +import ( + "math/rand" + "time" +) + +type Sidewinder struct{} + +type cells []*Cell + +func (r cells) sample() *Cell { + l := len(r) + if l == 0 { + return nil + } else { + return r[rand.Intn(l)] + } +} + +func (s *Sidewinder) On(g Grider) Grider { + rand.Seed(time.Now().UnixNano()) + for r := range g.EachRow() { + run := make(cells, 0) + for _, c := range r { + run = append(run, c) + + atEasternBoundary := c.East == nil + atNorthernBoundary := c.North == nil + shouldCloseOut := atEasternBoundary || (!atNorthernBoundary && rand.Intn(2) == 0) + + if shouldCloseOut { + member := run.sample() + if member.North != nil { + member.Link(member.North, true) + } + run = nil // Clear out the cells + } else { + c.Link(c.East, true) + } + } + } + return g +}