src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }