maze.go (3347B)
1 package maze 2 3 import ( 4 "bytes" 5 6 "code.dwrz.net/src/pkg/log" 7 "code.dwrz.net/src/pkg/minotaur/maze/cell" 8 "code.dwrz.net/src/pkg/minotaur/maze/direction" 9 "code.dwrz.net/src/pkg/minotaur/maze/position" 10 "code.dwrz.net/src/pkg/terminal" 11 ) 12 13 type Maze struct { 14 height int 15 layout [][]cell.Cell 16 log *log.Logger 17 width int 18 } 19 20 type Parameters struct { 21 Height int 22 Log *log.Logger 23 Width int 24 } 25 26 func New(p Parameters) (*Maze, error) { 27 return &Maze{ 28 height: p.Height, 29 layout: generate(p.Height, p.Width), 30 log: p.Log, 31 width: p.Width, 32 }, nil 33 } 34 35 func (m *Maze) Cell(p position.Position) cell.Cell { 36 return m.layout[p.Y][p.X] 37 } 38 39 func (m *Maze) InBounds(p position.Position) bool { 40 return inBounds(m.height, m.width, p.X, p.Y) 41 } 42 43 func (m *Maze) Solve(start, end position.Position) (s []position.Position) { 44 var q = [][]position.Position{{start}} 45 for len(q) > 0 { 46 // Get the current search from the queue. 47 s, q = q[0], q[1:] 48 49 // Use the last cell as the current candidate. 50 var ( 51 current = s[len(s)-1] 52 cell = m.Cell(current) 53 previous *position.Position 54 ) 55 56 // If we've reached the end, we have a solution. 57 if current.Equal(end) { 58 return s 59 } 60 61 // Explore new routes. 62 // Determine the previous cell, to avoid backtracking. 63 if len(s) > 1 { 64 previous = &s[len(s)-2] 65 } 66 for _, d := range direction.Directions() { 67 if !cell.HasDirection(d) { 68 continue 69 } 70 71 // Get the next position with this direction. 72 next := current.WithDirection(d) 73 74 // Prevent backtracking. 75 if previous != nil && next.Equal(*previous) { 76 continue 77 } 78 79 // Next is valid; add it to the search queue. 80 // TODO: is there a way to reuse the backing array? 81 var route = make([]position.Position, len(s)+1) 82 copy(route, s) 83 route[len(route)-1] = next 84 85 q = append(q, route) 86 } 87 } 88 89 return nil 90 } 91 92 type RenderParameters struct { 93 Passage string 94 // Positions is used to render a cell with the mapped string. 95 // The order in which you list positions matters. 96 // If there are overlapping positions, the last set one wins. 97 Positions map[position.Position]string 98 Wall string 99 } 100 101 func (m *Maze) Render(p RenderParameters) []byte { 102 var buf bytes.Buffer 103 104 buf.WriteString(terminal.CursorTopLeft) 105 106 // Print the top wall. 107 // We need to print each cell, and its right connection. 108 // Thus the wall needs to be twice the width. 109 // We also need to cover the right wall, thus +1. 110 topWidth := 2*m.width + 1 111 for i := 0; i < topWidth; i++ { 112 buf.WriteString(p.Wall) 113 } 114 buf.WriteString("\r\n") 115 116 for y, row := range m.layout { 117 buf.WriteString(p.Wall) // left wall 118 119 // Print each cell and the passage or wall to its right. 120 for x, cell := range row { 121 var block = p.Positions[position.Position{X: x, Y: y}] 122 if block == "" { 123 block = p.Passage 124 } 125 126 buf.WriteString(block) 127 128 if cell.HasDirection(direction.Right) { 129 buf.WriteString(p.Passage) 130 } else { 131 buf.WriteString(p.Wall) 132 } 133 } 134 buf.WriteString("\r\n") 135 136 // Print the cell's bottom passage or wall. 137 // Print a wall to the cell's lower-right. 138 buf.WriteString(p.Wall) // left wall 139 for _, cell := range row { 140 if cell.HasDirection(direction.Down) { 141 buf.WriteString(p.Passage) 142 } else { 143 buf.WriteString(p.Wall) 144 } 145 146 buf.WriteString(p.Wall) 147 } 148 buf.WriteString("\r\n") 149 } 150 151 return buf.Bytes() 152 }