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