Class: Theseus::OrthogonalMaze
- Defined in:
- lib/theseus/orthogonal_maze.rb
Overview
An orthogonal maze is one in which the field is tesselated into squares. This is probably the type of maze that most people think of, when they think of mazes.
The orthogonal maze implementation in Theseus is the most complete, supporting weaving as well as all four symmetry types. You can even convert any “perfect” (no loops) orthogonal maze to a “unicursal” maze. (Unicursal means “one course”, and refers to a maze that has no junctions, only a single path that takes you through every cell in the maze exactly once.)
maze = Theseus::OrthogonalMaze.generate(width: 10)
puts maze
Constant Summary
Constants inherited from Maze
Maze::E, Maze::N, Maze::NE, Maze::NW, Maze::PRIMARY, Maze::RESERVED, Maze::S, Maze::SE, Maze::SW, Maze::UNDER, Maze::UNDER_SHIFT, Maze::W
Instance Attribute Summary
Attributes inherited from Maze
#algorithm, #braid, #entrance, #exit, #height, #mask, #randomness, #symmetry, #weave, #width, #wrap
Instance Method Summary collapse
-
#finish! ⇒ Object
Extends Maze#finish! to make sure symmetrical mazes are properly closed.
-
#potential_exits_at(x, y) ⇒ Object
:nodoc:.
-
#to_unicursal(options = {}) ⇒ Object
Takes the current orthogonal maze and converts it into a unicursal maze.
Methods inherited from Maze
#[], #[]=, #add_opening_from, #adjacent_point, #apply_move_at, #clockwise, #counter_clockwise, #dead?, #dead_ends, #dx, #dy, #finish, generate, #generate!, #generated?, #hmirror, #initialize, #inspect, #move, #new_path, #new_solver, #opposite, #perform_weave, #relative_direction, #row_length, #solve, #sparsify!, #start, #step, #to, #to_s, #type, #valid?, #vmirror, #weave_allowed?, #wrap_x?, #wrap_y?
Constructor Details
This class inherits a constructor from Theseus::Maze
Instance Method Details
#finish! ⇒ Object
Extends Maze#finish! to make sure symmetrical mazes are properly closed. – Eventually, this would be good to generalize somehow, and make available to the other maze types. ++
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/theseus/orthogonal_maze.rb', line 25 def finish! #:nodoc: # for symmetrical mazes, if the size of the maze in the direction of reflection is # even, then we have two distinct halves that need to be joined in order for the # maze to be fully connected. available_width, available_height = @width, @height case @symmetry when :x then available_width = available_width / 2 when :y then available_height = available_height / 2 when :xy, :radial then available_width = available_width / 2 available_height = available_height / 2 end connector = lambda do |x, y, ix, iy, dir| start_x, start_y = x, y while @cells[y][x] == 0 y = (y + iy) % available_height x = (x + ix) % available_width break if start_x == x || start_y == y end if @cells[y][x] == 0 warn "maze cannot be fully connected" nil else @cells[y][x] |= dir nx, ny = move(x, y, dir) @cells[ny][nx] |= opposite(dir) [x,y] end end even = lambda { |x| x % 2 == 0 } case @symmetry when :x then connector[available_width-1, rand(available_height), 0, 1, E] if even[@width] when :y then connector[rand(available_width), available_height-1, 1, 0, S] if even[@height] when :xy then if even[@width] x, y = connector[available_width-1, rand(available_height), 0, 1, E] @cells[@height-y-1][x] |= E @cells[@height-y-1][x+1] |= W end if even[@height] x, y = connector[rand(available_width), available_height-1, 1, 0, S] @cells[y][@width-x-1] |= S @cells[y+1][@width-x-1] |= N end when :radial then if even[@width] @cells[available_height-1][available_width-1] |= E | S @cells[available_height-1][available_width] |= W | S @cells[available_height][available_width-1] |= E | N @cells[available_height][available_width] |= W | N end end super end |
#potential_exits_at(x, y) ⇒ Object
:nodoc:
16 17 18 |
# File 'lib/theseus/orthogonal_maze.rb', line 16 def potential_exits_at(x, y) #:nodoc: [N, S, E, W] end |
#to_unicursal(options = {}) ⇒ Object
Takes the current orthogonal maze and converts it into a unicursal maze. A unicursal maze is one with only a single path, and no dead-ends or junctions. Such mazes are more properly called “labyrinths”. Note that although this method will always return a new OrthogonalMaze instance, it is not guaranteed to be a valid maze unless the current maze is “perfect” (not braided, containing no loops).
The resulting unicursal maze will be twice as wide and twice as high as the original maze.
The options hash can be used to specify the :entrance and :exit points for the resulting maze. Currently, both the entrance and the exit must be adjacent.
The process of converting an orthogonal maze to a unicursal maze is straightforward; take the maze, and divide all passages in half down the middle, making two passages. Dead-ends become a u-turn, etc. This is why the maze increases in size.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/theseus/orthogonal_maze.rb', line 108 def to_unicursal(={}) unicursal = OrthogonalMaze.new(.merge(width: @width*2, height: @height*2, prebuilt: true)) set = lambda do |x, y, direction, *recip| nx, ny = move(x, y, direction) unicursal[x,y] |= direction unicursal[nx, ny] |= opposite(direction) if recip[0] end @cells.each_with_index do |row, y| row.each_with_index do |cell, x| x2 = x * 2 y2 = y * 2 if cell & N != 0 set[x2, y2, N] set[x2+1, y2, N] set[x2, y2+1, N, true] if cell & W == 0 set[x2+1, y2+1, N, true] if cell & E == 0 set[x2, y2+1, E, true] if (cell & PRIMARY) == N end if cell & S != 0 set[x2, y2+1, S] set[x2+1, y2+1, S] set[x2, y2, S, true] if cell & W == 0 set[x2+1, y2, S, true] if cell & E == 0 set[x2, y2, E, true] if (cell & PRIMARY) == S end if cell & W != 0 set[x2, y2, W] set[x2, y2+1, W] set[x2+1, y2, W, true] if cell & N == 0 set[x2+1, y2+1, W, true] if cell & S == 0 set[x2+1, y2, S, true] if (cell & PRIMARY) == W end if cell & E != 0 set[x2+1, y2, E] set[x2+1, y2+1, E] set[x2, y2, E, true] if cell & N == 0 set[x2, y2+1, E, true] if cell & S == 0 set[x2, y2, S, true] if (cell & PRIMARY) == E end if cell & (N << UNDER_SHIFT) != 0 unicursal[x2, y2] |= (N | S) << UNDER_SHIFT unicursal[x2+1, y2] |= (N | S) << UNDER_SHIFT unicursal[x2, y2+1] |= (N | S) << UNDER_SHIFT unicursal[x2+1, y2+1] |= (N | S) << UNDER_SHIFT elsif cell & (W << UNDER_SHIFT) != 0 unicursal[x2, y2] |= (E | W) << UNDER_SHIFT unicursal[x2+1, y2] |= (E | W) << UNDER_SHIFT unicursal[x2, y2+1] |= (E | W) << UNDER_SHIFT unicursal[x2+1, y2+1] |= (E | W) << UNDER_SHIFT end end end enter_at = unicursal.adjacent_point(unicursal.entrance) exit_at = unicursal.adjacent_point(unicursal.exit) if enter_at && exit_at unicursal.add_opening_from(unicursal.entrance) unicursal.add_opening_from(unicursal.exit) if enter_at[0] < exit_at[0] unicursal[enter_at[0], enter_at[1]] &= ~E unicursal[enter_at[0]+1, enter_at[1]] &= ~W elsif enter_at[1] < exit_at[1] unicursal[enter_at[0], enter_at[1]] &= ~S unicursal[enter_at[0], enter_at[1]+1] &= ~N end end return unicursal end |