diff --git a/README.md b/README.md index b360f98..91a1b7d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,59 @@ -# Mazes +# Mazes Chapter 2 -Based on the book [Mazes for Programmers: Code Your Own Twisty Little Passages](https://pragprog.com/book/jbmaze/mazes-for-programmers) +Chapter 2 is all about building a grid class that contains cells. We can +populate our maze and print it out. + +Running this produces a random maze with the btree algorithm + +``` +Binary Tree ++---+---+---+---+---+---+---+---+---+---+ +| | ++ + + + + +---+---+---+---+ + +| | | | | | | ++---+ +---+---+ +---+---+ +---+ + +| | | | | ++---+ +---+---+---+---+---+ +---+ + +| | | | ++ + + +---+ +---+---+---+ + + +| | | | | | | ++---+ + +---+ +---+ +---+---+ + +| | | | | | ++---+---+---+---+---+---+---+---+---+ + +| | ++ +---+---+---+ + +---+ +---+ + +| | | | | | ++---+---+---+ + + +---+ + + + +| | | | | | | ++ +---+ +---+---+ + +---+ + + +| | | | | | | ++---+---+---+---+---+---+---+---+---+---+ + +Sidewinder ++---+---+---+---+---+---+---+---+---+---+ +| | ++ + +---+---+ +---+---+ + + + +| | | | | | | ++ + +---+ +---+---+ +---+ + + +| | | | | | | ++ + + +---+ +---+---+---+ + + +| | | | | | | ++ + + + + +---+ +---+---+ + +| | | | | | | | ++ + +---+---+ + +---+ + +---+ +| | | | | | | ++---+ + +---+ + + +---+ + + +| | | | | | | | ++---+ +---+ +---+ + +---+ + + +| | | | | | | ++---+ +---+ +---+ + + + +---+ +| | | | | | | ++ + +---+ + +---+---+ +---+---+ +| | | | | | ++---+---+---+---+---+---+---+---+---+---+ +``` + +![Image output](test.png) + +The following code is just a port of the ruby code in the book -All code are in the chapter branches diff --git a/btree.go b/btree.go new file mode 100644 index 0000000..3ce6f94 --- /dev/null +++ b/btree.go @@ -0,0 +1,29 @@ +package main + +import ( + "math/rand" + "time" +) + +type BinaryTree struct{} + +func (b *BinaryTree) On(g *Grid) { + 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..99f963e --- /dev/null +++ b/cell.go @@ -0,0 +1,73 @@ +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 +} diff --git a/grid.go b/grid.go new file mode 100644 index 0000000..6641d14 --- /dev/null +++ b/grid.go @@ -0,0 +1,128 @@ +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) 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 := " " + 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/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..d78595e --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +// 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 := NewGrid(10, 10) + b := new(BinaryTree) + b.On(g) + fmt.Println(g) + fmt.Println("Sidewinder") + g = NewGrid(10, 10) + s := new(Sidewinder) + s.On(g) + fmt.Println(g) + fmt.Println("Showing image") + i := NewImage(40) + i.Save("test.png", i.DrawGrid(g)) + Show("test.png") +} diff --git a/sidewinder.go b/sidewinder.go new file mode 100644 index 0000000..d83c541 --- /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 *Grid) *Grid { + 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 +} diff --git a/test.png b/test.png new file mode 100644 index 0000000..5da2bd2 Binary files /dev/null and b/test.png differ