|
ferencd@0
|
1 #---
|
|
ferencd@0
|
2 # Excerpted from "Mazes for Programmers",
|
|
ferencd@0
|
3 # published by The Pragmatic Bookshelf.
|
|
ferencd@0
|
4 # Copyrights apply to this code. It may not be used to create training material,
|
|
ferencd@0
|
5 # courses, books, articles, and the like. Contact us if you are in doubt.
|
|
ferencd@0
|
6 # We make no guarantees that this code is fit for any purpose.
|
|
ferencd@0
|
7 # Visit http://www.pragmaticprogrammer.com/titles/jbmaze for more book information.
|
|
ferencd@0
|
8 #---
|
|
ferencd@0
|
9 require 'cell'
|
|
ferencd@0
|
10
|
|
ferencd@0
|
11 # Corner constants, to know where to put a torch
|
|
ferencd@0
|
12 TOP_LEFT = 1
|
|
ferencd@0
|
13 TOP_RIGHT = 2
|
|
ferencd@0
|
14 BOTTOM_LEFT = 4
|
|
ferencd@0
|
15 BOTTOM_RIGHT = 8
|
|
ferencd@0
|
16
|
|
ferencd@0
|
17 class Room
|
|
ferencd@0
|
18 attr_reader :x1, :y1, :x2, :y2
|
|
ferencd@0
|
19
|
|
ferencd@0
|
20 def initialize(px1,py1,px2,py2)
|
|
ferencd@0
|
21 @x1 = px1
|
|
ferencd@0
|
22 @x2 = px2
|
|
ferencd@0
|
23 @y1 = py1
|
|
ferencd@0
|
24 @y2 = py2
|
|
ferencd@0
|
25 end
|
|
ferencd@0
|
26
|
|
ferencd@0
|
27 def in(x, y)
|
|
ferencd@0
|
28 return x > @x1-1 && x < @x2+1 && y > @y1-1 && y < @y2+1
|
|
ferencd@0
|
29 end
|
|
ferencd@0
|
30 end
|
|
ferencd@0
|
31
|
|
ferencd@0
|
32
|
|
ferencd@0
|
33 class Grid
|
|
ferencd@0
|
34 attr_reader :rows, :columns, :dead_ends
|
|
ferencd@0
|
35
|
|
ferencd@0
|
36 def initialize(rows, columns)
|
|
ferencd@0
|
37 @rows = rows
|
|
ferencd@0
|
38 @columns = columns
|
|
ferencd@0
|
39 @grid = prepare_grid
|
|
ferencd@0
|
40 @rooms = []
|
|
ferencd@0
|
41
|
|
ferencd@0
|
42 # Where food will go
|
|
ferencd@0
|
43 @dead_ends = {}
|
|
ferencd@0
|
44 @dead_ends[1] = []
|
|
ferencd@0
|
45 @dead_ends[2] = []
|
|
ferencd@0
|
46 @dead_ends[4] = []
|
|
ferencd@0
|
47 @dead_ends[8] = []
|
|
ferencd@0
|
48
|
|
ferencd@0
|
49 configure_cells
|
|
ferencd@0
|
50
|
|
ferencd@0
|
51 end
|
|
ferencd@0
|
52
|
|
ferencd@0
|
53 def prepare_grid
|
|
ferencd@0
|
54 Array.new(rows) do |row|
|
|
ferencd@0
|
55 Array.new(columns) do |column|
|
|
ferencd@0
|
56 Cell.new(row, column)
|
|
ferencd@0
|
57 end
|
|
ferencd@0
|
58 end
|
|
ferencd@0
|
59 end
|
|
ferencd@0
|
60
|
|
ferencd@0
|
61 def configure_cells
|
|
ferencd@0
|
62 each_cell do |cell|
|
|
ferencd@0
|
63 row, col = cell.row, cell.column
|
|
ferencd@0
|
64
|
|
ferencd@0
|
65 cell.north = self[row - 1, col]
|
|
ferencd@0
|
66 cell.south = self[row + 1, col]
|
|
ferencd@0
|
67 cell.west = self[row, col - 1]
|
|
ferencd@0
|
68 cell.east = self[row, col + 1]
|
|
ferencd@0
|
69 end
|
|
ferencd@0
|
70 end
|
|
ferencd@0
|
71
|
|
ferencd@0
|
72 #
|
|
ferencd@0
|
73 # Creates a room in the grid
|
|
ferencd@0
|
74 #
|
|
ferencd@0
|
75 def create_room(x1,y1, x2,y2)
|
|
ferencd@0
|
76 # Firstly: empty the room
|
|
ferencd@0
|
77 puts "x1:#{x1}, y1, x2, y2"
|
|
ferencd@0
|
78 (x1..x2).each do |col|
|
|
ferencd@0
|
79 (y1..y2).each do |row|
|
|
ferencd@0
|
80 @grid[row][col].link(@grid[row][col+1]) # -> Next to right
|
|
ferencd@0
|
81 @grid[row][col].link(@grid[row+1][col]) # -> Next to down
|
|
ferencd@0
|
82 end
|
|
ferencd@0
|
83 end
|
|
ferencd@0
|
84 # then create the surrounding walls
|
|
ferencd@0
|
85 (x1 + 1..x2-1).each do |col|
|
|
ferencd@0
|
86 @grid[y1+1][col].unlink(@grid[y1][col])
|
|
ferencd@0
|
87 @grid[y2-1][col].unlink(@grid[y2][col])
|
|
ferencd@0
|
88 end
|
|
ferencd@0
|
89
|
|
ferencd@0
|
90 (y1+1..y2-1).each do |row|
|
|
ferencd@0
|
91 @grid[row][x1+1].unlink(@grid[row][x1])
|
|
ferencd@0
|
92 @grid[row][x2-1].unlink(@grid[row][x2])
|
|
ferencd@0
|
93 end
|
|
ferencd@0
|
94
|
|
ferencd@0
|
95 r = Room.new(x1,y1,x2,y2)
|
|
ferencd@0
|
96 @rooms << r
|
|
ferencd@0
|
97
|
|
ferencd@0
|
98 end
|
|
ferencd@0
|
99
|
|
ferencd@0
|
100 def [](row, column)
|
|
ferencd@0
|
101 return nil unless row.between?(0, @rows - 1)
|
|
ferencd@0
|
102 return nil unless column.between?(0, @grid[row].count - 1)
|
|
ferencd@0
|
103 @grid[row][column]
|
|
ferencd@0
|
104 end
|
|
ferencd@0
|
105
|
|
ferencd@0
|
106 def random_cell
|
|
ferencd@0
|
107 row = rand(@rows)
|
|
ferencd@0
|
108 column = rand(@grid[row].count)
|
|
ferencd@0
|
109
|
|
ferencd@0
|
110 self[row, column]
|
|
ferencd@0
|
111 end
|
|
ferencd@0
|
112
|
|
ferencd@0
|
113 def size
|
|
ferencd@0
|
114 @rows * @columns
|
|
ferencd@0
|
115 end
|
|
ferencd@0
|
116
|
|
ferencd@0
|
117 def each_row
|
|
ferencd@0
|
118 @grid.each do |row|
|
|
ferencd@0
|
119 yield row
|
|
ferencd@0
|
120 end
|
|
ferencd@0
|
121 end
|
|
ferencd@0
|
122
|
|
ferencd@0
|
123 def each_cell
|
|
ferencd@0
|
124 each_row do |row|
|
|
ferencd@0
|
125 row.each do |cell|
|
|
ferencd@0
|
126 yield cell if cell
|
|
ferencd@0
|
127 end
|
|
ferencd@0
|
128 end
|
|
ferencd@0
|
129 end
|
|
ferencd@0
|
130
|
|
ferencd@0
|
131 # By default, we'll simply display a cell as a blank space. We'll use this
|
|
ferencd@0
|
132 # to add other ways of displaying cells.
|
|
ferencd@0
|
133 def contents_of(cell)
|
|
ferencd@0
|
134 ' '
|
|
ferencd@0
|
135 end
|
|
ferencd@0
|
136 #
|
|
ferencd@0
|
137 def background_color_for(cell)
|
|
ferencd@0
|
138 nil
|
|
ferencd@0
|
139 end
|
|
ferencd@0
|
140
|
|
ferencd@0
|
141 def to_s
|
|
ferencd@0
|
142 maze_js_array
|
|
ferencd@0
|
143 end
|
|
ferencd@0
|
144
|
|
ferencd@0
|
145 #
|
|
ferencd@0
|
146 # Returns the number for the given cell
|
|
ferencd@0
|
147 #
|
|
ferencd@0
|
148 def number_for_cell(cell)
|
|
ferencd@0
|
149 number = 0
|
|
ferencd@0
|
150 number |= EAST if cell.linked?(cell.east)
|
|
ferencd@0
|
151 number |= NORTH if cell.linked?(cell.north)
|
|
ferencd@0
|
152 number |= SOUTH if cell.linked?(cell.south)
|
|
ferencd@0
|
153 number |= WEST if cell.linked?(cell.west)
|
|
ferencd@0
|
154 number
|
|
ferencd@0
|
155 end
|
|
ferencd@0
|
156
|
|
ferencd@0
|
157 #
|
|
ferencd@0
|
158 # Returns the maze as a javascript array. This also will initialize all the dead ends
|
|
ferencd@0
|
159 # where we will put food or jevelry items
|
|
ferencd@0
|
160 #
|
|
ferencd@0
|
161 def maze_js_array
|
|
ferencd@0
|
162 output = "var maze = [\n"
|
|
ferencd@0
|
163 dist_strings = []
|
|
ferencd@0
|
164
|
|
ferencd@0
|
165 each_row do |row|
|
|
ferencd@0
|
166 # num array, the number for the row
|
|
ferencd@0
|
167 numarray = []
|
|
ferencd@0
|
168 row_string = ''
|
|
ferencd@0
|
169
|
|
ferencd@0
|
170 row.each do |cell|
|
|
ferencd@0
|
171 cell = Cell.new(-1, -1) unless cell
|
|
ferencd@0
|
172 cell_nr = number_for_cell(cell)
|
|
ferencd@0
|
173 numarray << cell_nr
|
|
ferencd@0
|
174
|
|
ferencd@0
|
175 # Add this cell as a dead end destination
|
|
ferencd@0
|
176 @dead_ends[cell_nr] << cell if [1,2,4,8].include? cell_nr
|
|
ferencd@0
|
177
|
|
ferencd@0
|
178 end
|
|
ferencd@0
|
179
|
|
ferencd@0
|
180 row_string << '[' << numarray.join(', ') << ']'
|
|
ferencd@0
|
181 dist_strings << row_string
|
|
ferencd@0
|
182
|
|
ferencd@0
|
183 end
|
|
ferencd@0
|
184 output << dist_strings.join( ",\n" )
|
|
ferencd@0
|
185 output << "];\n"
|
|
ferencd@0
|
186 output
|
|
ferencd@0
|
187 end
|
|
ferencd@0
|
188
|
|
ferencd@0
|
189 def direction(d)
|
|
ferencd@0
|
190 return 'N' if d == NORTH
|
|
ferencd@0
|
191 return 'W' if d == WEST
|
|
ferencd@0
|
192 return 'E' if d == EAST
|
|
ferencd@0
|
193 return 'S' if d == SOUTH
|
|
ferencd@0
|
194 return '?'
|
|
ferencd@0
|
195 end
|
|
ferencd@0
|
196
|
|
ferencd@0
|
197 #
|
|
ferencd@0
|
198 # Returns the distances as a js array
|
|
ferencd@0
|
199 #
|
|
ferencd@0
|
200 def distances_js_array_path
|
|
ferencd@0
|
201 output = ''
|
|
ferencd@0
|
202 dist_strings = []
|
|
ferencd@0
|
203
|
|
ferencd@0
|
204 each_row do |row|
|
|
ferencd@0
|
205 row.each do |cell|
|
|
ferencd@0
|
206 cell = Cell.new(-1, -1) unless cell
|
|
ferencd@0
|
207 conts = contents_of(cell).to_s
|
|
ferencd@0
|
208 if conts.strip.length > 0
|
|
ferencd@0
|
209 dist_strings << {:idx => conts.to_i, :row => cell.row, :col => cell.column}
|
|
ferencd@0
|
210 end
|
|
ferencd@0
|
211 end
|
|
ferencd@0
|
212 end
|
|
ferencd@0
|
213 sorted = dist_strings.sort_by {|el| el[:idx]}
|
|
ferencd@0
|
214
|
|
ferencd@0
|
215 # The directions this will go
|
|
ferencd@0
|
216 walk_string = ''
|
|
ferencd@0
|
217 # Now create a direction array from the elements
|
|
ferencd@0
|
218 current_cell = self[sorted[0][:row], sorted[0][:col] ]
|
|
ferencd@0
|
219 sorted.each_with_index do |cell, index|
|
|
ferencd@0
|
220 if index > 0
|
|
ferencd@0
|
221 # Find the direction from curent_cell to cell
|
|
ferencd@0
|
222 the_cell = self[cell[:row], cell[:col] ]
|
|
ferencd@0
|
223 dest = direction(current_cell.direction(the_cell))
|
|
ferencd@0
|
224 walk_string << dest
|
|
ferencd@0
|
225 current_cell = the_cell
|
|
ferencd@0
|
226 end
|
|
ferencd@0
|
227 end
|
|
ferencd@0
|
228 output << "#{walk_string}"
|
|
ferencd@0
|
229 output
|
|
ferencd@0
|
230 end
|
|
ferencd@0
|
231
|
|
ferencd@0
|
232 #
|
|
ferencd@0
|
233 # Returns the distances as a js array
|
|
ferencd@0
|
234 #
|
|
ferencd@0
|
235 def distances_js_array
|
|
ferencd@0
|
236 output = "var distances = [\n"
|
|
ferencd@0
|
237 dist_strings = []
|
|
ferencd@0
|
238
|
|
ferencd@0
|
239 each_row do |row|
|
|
ferencd@0
|
240
|
|
ferencd@0
|
241 row_string = ''
|
|
ferencd@0
|
242 numarray = []
|
|
ferencd@0
|
243
|
|
ferencd@0
|
244 row.each do |cell|
|
|
ferencd@0
|
245 cell = Cell.new(-1, -1) unless cell
|
|
ferencd@0
|
246 conts = contents_of(cell).to_s
|
|
ferencd@0
|
247 if conts.strip.length > 0
|
|
ferencd@0
|
248 numarray << conts
|
|
ferencd@0
|
249 else
|
|
ferencd@0
|
250 numarray << '-1'
|
|
ferencd@0
|
251 end
|
|
ferencd@0
|
252 end
|
|
ferencd@0
|
253 row_string << '[' << numarray.join(', ') << ']'
|
|
ferencd@0
|
254 dist_strings << row_string
|
|
ferencd@0
|
255 end
|
|
ferencd@0
|
256 output << dist_strings.join(",\n")
|
|
ferencd@0
|
257 output << "];\n"
|
|
ferencd@0
|
258 output
|
|
ferencd@0
|
259 end
|
|
ferencd@0
|
260
|
|
ferencd@0
|
261 def maze_serialized_text
|
|
ferencd@0
|
262 output = []
|
|
ferencd@0
|
263 each_row do |row|
|
|
ferencd@0
|
264 numarray = []
|
|
ferencd@0
|
265 row.each do |cell|
|
|
ferencd@0
|
266 cell = Cell.new(-1, -1) unless cell
|
|
ferencd@0
|
267 numarray << number_for_cell(cell)
|
|
ferencd@0
|
268 end
|
|
ferencd@0
|
269 output << numarray.join(',')
|
|
ferencd@0
|
270 end
|
|
ferencd@0
|
271 output.join(':')
|
|
ferencd@0
|
272 end
|
|
ferencd@0
|
273
|
|
ferencd@0
|
274 def deadends
|
|
ferencd@0
|
275 list = []
|
|
ferencd@0
|
276
|
|
ferencd@0
|
277 each_cell do |cell|
|
|
ferencd@0
|
278 list << cell if cell.links.count == 1
|
|
ferencd@0
|
279 end
|
|
ferencd@0
|
280
|
|
ferencd@0
|
281 list
|
|
ferencd@0
|
282 end
|
|
ferencd@0
|
283
|
|
ferencd@0
|
284 def braid(p=1.0)
|
|
ferencd@0
|
285 deadends.shuffle.each do |cell|
|
|
ferencd@0
|
286 next if cell.links.count != 1 || rand > p
|
|
ferencd@0
|
287
|
|
ferencd@0
|
288 neighbors = cell.neighbors.reject { |n| cell.linked?(n) }
|
|
ferencd@0
|
289 best = neighbors.select { |n| n.links.count == 1 }
|
|
ferencd@0
|
290 best = neighbors if best.empty?
|
|
ferencd@0
|
291
|
|
ferencd@0
|
292 neighbor = best.sample
|
|
ferencd@0
|
293 cell.link(neighbor)
|
|
ferencd@0
|
294 end
|
|
ferencd@0
|
295 end
|
|
ferencd@0
|
296
|
|
ferencd@0
|
297
|
|
ferencd@0
|
298 #
|
|
ferencd@0
|
299 # Tells us if we can make a room at the given coordinates
|
|
ferencd@0
|
300 #
|
|
ferencd@0
|
301 def can_make_room(x1, y1, x2, y2)
|
|
ferencd@0
|
302
|
|
ferencd@0
|
303 @rooms.each do |room|
|
|
ferencd@0
|
304 return FALSE if room.in(x1, y1)
|
|
ferencd@0
|
305 return FALSE if room.in(x2, y2)
|
|
ferencd@0
|
306 return FALSE if room.in(x1, y2)
|
|
ferencd@0
|
307 return FALSE if room.in(x2, y1)
|
|
ferencd@0
|
308 end
|
|
ferencd@0
|
309
|
|
ferencd@0
|
310 TRUE
|
|
ferencd@0
|
311 end
|
|
ferencd@0
|
312
|
|
ferencd@0
|
313 #
|
|
ferencd@0
|
314 # Creates a list of locations where torches can be found, mostly in corners at unfinshed walls
|
|
ferencd@0
|
315 #
|
|
ferencd@0
|
316 def identify_torch_locations
|
|
ferencd@0
|
317 numarray = []
|
|
ferencd@0
|
318 j = 0
|
|
ferencd@0
|
319 each_row do |row|
|
|
ferencd@0
|
320 i = 0
|
|
ferencd@0
|
321 row.each do |cell|
|
|
ferencd@0
|
322 number = 0
|
|
ferencd@0
|
323 number |= TOP_RIGHT if cell.east && cell.north && cell.linked?(cell.east) && cell.linked?(cell.north) && !cell.east.linked?(cell.east.north) && !cell.north.linked?(cell.north.east)
|
|
ferencd@0
|
324 number |= TOP_LEFT if cell.west && cell.north && cell.linked?(cell.west) && cell.linked?(cell.north) && !cell.west.linked?(cell.west.north) && !cell.north.linked?(cell.north.west)
|
|
ferencd@0
|
325 number |= BOTTOM_RIGHT if cell.east && cell.south && cell.linked?(cell.east) && cell.linked?(cell.south) && !cell.east.linked?(cell.east.south) && !cell.south.linked?(cell.south.east)
|
|
ferencd@0
|
326 number |= BOTTOM_LEFT if cell.west && cell.south && cell.linked?(cell.west) && cell.linked?(cell.south) && !cell.west.linked?(cell.west.south) && !cell.south.linked?(cell.south.west)
|
|
ferencd@0
|
327 if number != 0
|
|
ferencd@0
|
328 torch_data = { :i => i, :j => j, :number => number}
|
|
ferencd@0
|
329 numarray << torch_data
|
|
ferencd@0
|
330 end
|
|
ferencd@0
|
331 i += 1
|
|
ferencd@0
|
332 end
|
|
ferencd@0
|
333 j += 1
|
|
ferencd@0
|
334 end
|
|
ferencd@0
|
335 numarray
|
|
ferencd@0
|
336 end
|
|
ferencd@0
|
337
|
|
ferencd@0
|
338 end
|