annotate server.rb @ 0:1eef88068f9f tip

initial commit of maze game source
author ferencd
date Sun, 15 Sep 2019 11:46:47 +0200
parents
children
rev   line source
ferencd@0 1 require 'net/http/server'
ferencd@0 2 require 'pp'
ferencd@0 3 require 'ellers'
ferencd@0 4 require 'wilsons'
ferencd@0 5 require 'distance_grid'
ferencd@0 6 require 'pg'
ferencd@0 7 require 'base62-rb'
ferencd@0 8 require 'zlib'
ferencd@0 9 require 'wlang'
ferencd@0 10 require 'set'
ferencd@0 11 require 'descends'
ferencd@0 12 require 'pg'
ferencd@0 13 require 'database'
ferencd@0 14 require 'openssl'
ferencd@0 15 require 'game'
ferencd@0 16 require 'gs_logger'
ferencd@0 17 require 'gamedata'
ferencd@0 18
ferencd@0 19 #
ferencd@0 20 # Starting the App, updating things that need to be
ferencd@0 21 #
ferencd@0 22 $LOG.info 'gameserver 0.1 starting'
ferencd@0 23 Database.initialize
ferencd@0 24
ferencd@0 25 #
ferencd@0 26 # Constants
ferencd@0 27 #
ferencd@0 28 CONTENT_TYPE = 'Content-Type'
ferencd@0 29 MAIN_HTML = 'lab.html'
ferencd@0 30 CONTENT_TYPES =
ferencd@0 31 {
ferencd@0 32 :png => 'image/png',
ferencd@0 33 :gif => 'image/gif',
ferencd@0 34 :js => 'application/javascript'
ferencd@0 35 }
ferencd@0 36
ferencd@0 37 GAME_TYPE_MAZE = 1
ferencd@0 38 GAME_TYPE_ADVENTURE = 2
ferencd@0 39 GAME_TYPE_STORY = 3
ferencd@0 40
ferencd@0 41 #################################################################################################################
ferencd@0 42 # Helper classes
ferencd@0 43 #################################################################################################################
ferencd@0 44
ferencd@0 45 #
ferencd@0 46 # Used to get some extra numbers
ferencd@0 47 #
ferencd@0 48 class Integer
ferencd@0 49 N_BYTES = [42].pack('i').size
ferencd@0 50 N_BITS = N_BYTES * 16
ferencd@0 51 MAX = 2 ** (N_BITS - 2) - 1
ferencd@0 52 MIN = -MAX - 1
ferencd@0 53 end
ferencd@0 54
ferencd@0 55 #
ferencd@0 56 # An extension to the plain string class to provide b62 validations
ferencd@0 57 #
ferencd@0 58 class String
ferencd@0 59 def valid_b62?
ferencd@0 60 !!match(/^[[:alnum:]]+$/)
ferencd@0 61 end
ferencd@0 62
ferencd@0 63 def is_i?
ferencd@0 64 /\A[-+]?\d+\z/ === self
ferencd@0 65 end
ferencd@0 66
ferencd@0 67 def encrypt(key)
ferencd@0 68 cipher = OpenSSL::Cipher::Cipher.new('DES-EDE3-CBC').encrypt
ferencd@0 69 cipher.key = Digest::SHA1.hexdigest key
ferencd@0 70 s = cipher.update(self) + cipher.final
ferencd@0 71 s.unpack('H*')[0].upcase
ferencd@0 72 end
ferencd@0 73
ferencd@0 74 def decrypt(key)
ferencd@0 75 cipher = OpenSSL::Cipher::Cipher.new('DES-EDE3-CBC').decrypt
ferencd@0 76 cipher.key = Digest::SHA1.hexdigest key
ferencd@0 77 s = [self].pack('H*').unpack('C*').pack('c*')
ferencd@0 78 cipher.update(s) + cipher.final
ferencd@0 79 end
ferencd@0 80
ferencd@0 81 end
ferencd@0 82
ferencd@0 83 #
ferencd@0 84 # Simple class to NOT to escape HTML code in the wlang renderer
ferencd@0 85 #
ferencd@0 86 class SimpleHtmlRenderer < WLang::Dialect
ferencd@0 87
ferencd@0 88 def highlight(buf, fn)
ferencd@0 89 var_name = render(fn)
ferencd@0 90 var_value = evaluate(var_name)
ferencd@0 91 buf << var_value
ferencd@0 92 end
ferencd@0 93
ferencd@0 94 tag '$', :highlight
ferencd@0 95 end
ferencd@0 96
ferencd@0 97
ferencd@0 98 #################################################################################################################
ferencd@0 99 # Helper methods #
ferencd@0 100 #################################################################################################################
ferencd@0 101
ferencd@0 102 #
ferencd@0 103 # Provides a fully completed template for the template engine
ferencd@0 104 #
ferencd@0 105 def complete_templates(templates)
ferencd@0 106 (1..5).each do | v |
ferencd@0 107 templates["gotext#{v}".to_sym] = '' unless templates.has_key? "gotext#{v}".to_sym
ferencd@0 108 end
ferencd@0 109 templates
ferencd@0 110 end
ferencd@0 111
ferencd@0 112 #
ferencd@0 113 # Just returns page not found
ferencd@0 114 #
ferencd@0 115 def page_not_found
ferencd@0 116 content_type = {CONTENT_TYPE => 'text/html'}
ferencd@0 117 retv = [ '<html><center><strong>404 :-(</strong></center></html>' ]
ferencd@0 118 code = 404
ferencd@0 119 return code, content_type, retv
ferencd@0 120 end
ferencd@0 121
ferencd@0 122 def internal_server_error
ferencd@0 123 content_type = {CONTENT_TYPE => 'text/html'}
ferencd@0 124 retv = [ '<html><center><strong>500 :-(</strong></center></html>' ]
ferencd@0 125 code = 500
ferencd@0 126 return code, content_type, retv
ferencd@0 127 end
ferencd@0 128
ferencd@0 129
ferencd@0 130 #
ferencd@0 131 # Will create a random room in the grid
ferencd@0 132 #
ferencd@0 133 def create_room(grid)
ferencd@0 134
ferencd@0 135 tries = 0
ferencd@0 136 can_not_make = false
ferencd@0 137 begin
ferencd@0 138
ferencd@0 139 max_width = [grid.columns/10, 6].max
ferencd@0 140 max_height = [grid.rows/10, 6].max
ferencd@0 141
ferencd@0 142 grid_width = rand(2 .. max_width) + 2
ferencd@0 143 grid_height = rand(2 .. max_height) + 2
ferencd@0 144
ferencd@0 145 x1 = rand(grid.columns - grid_width - 4)
ferencd@0 146 x2 = x1 + grid_width
ferencd@0 147 y1 = rand(grid.rows - grid_height - 4)
ferencd@0 148 y2 = y1 + grid_height
ferencd@0 149
ferencd@0 150 tries += 1
ferencd@0 151 can_not_make = !grid.can_make_room(x1,y1,x2,y2)
ferencd@0 152
ferencd@0 153 end while can_not_make && tries > 5
ferencd@0 154
ferencd@0 155 return '' if tries >= 5
ferencd@0 156
ferencd@0 157 grid.create_room x1, y1, x2, y2
ferencd@0 158
ferencd@0 159 # Place the door
ferencd@0 160 door_x_cell = x1 + grid_width / 2
ferencd@0 161 door_y_top_cell = y1
ferencd@0 162 door_y_bottom_cell = y1 + 1
ferencd@0 163
ferencd@0 164 "{r:#{door_y_bottom_cell},c:#{door_x_cell},v:0}, {r:#{door_y_top_cell},c:#{door_x_cell},v:1}"
ferencd@0 165 end
ferencd@0 166
ferencd@0 167 #
ferencd@0 168 # Calculates the distance from the goal cell on the given grid towards the cell_to_go
ferencd@0 169 # and returns the final_len and final_path
ferencd@0 170 #
ferencd@0 171 def distance_calculator(goal, grid, cell_to_go, final_len, final_path)
ferencd@0 172 another_distances = goal.distances
ferencd@0 173 grid.distances = another_distances.path_to(cell_to_go)
ferencd@0 174 jsv2 = grid.distances_js_array_path
ferencd@0 175 final_len += jsv2.length
ferencd@0 176 final_path += jsv2
ferencd@0 177 return final_len, final_path
ferencd@0 178 end
ferencd@0 179
ferencd@0 180 #
ferencd@0 181 # Generates a path for the given walker
ferencd@0 182 #
ferencd@0 183 def create_walker_path(grid, r, c)
ferencd@0 184 # making a new path from the furthest point from there
ferencd@0 185 new_distances = grid[r, c].distances
ferencd@0 186
ferencd@0 187 # goal will be the cell which is the furthest from the new_start and max_dist the length of it
ferencd@0 188 goal, _ = new_distances.max
ferencd@0 189 grid.distances = new_distances.path_to(goal)
ferencd@0 190
ferencd@0 191 jsv = grid.distances_js_array_path
ferencd@0 192 final_path = jsv
ferencd@0 193 final_len = final_path.length
ferencd@0 194
ferencd@0 195 # And the goal is that the walker will take a round around the maze
ferencd@0 196
ferencd@0 197 # Now add in another path to (0,0)
ferencd@0 198 final_len, final_path = distance_calculator(goal, grid, grid[0, 0],final_len, final_path)
ferencd@0 199 final_len, final_path = distance_calculator(grid[0, 0], grid, grid[0, grid.columns - 1],final_len, final_path)
ferencd@0 200 final_len, final_path = distance_calculator(grid[0, grid.columns - 1], grid, grid[grid.rows - 1, grid.columns - 1],final_len, final_path)
ferencd@0 201
ferencd@0 202 s = ''
ferencd@0 203 s << "path_len:#{final_len},r:#{r},c:#{c},p:'#{final_path}'"
ferencd@0 204 s
ferencd@0 205
ferencd@0 206 end
ferencd@0 207
ferencd@0 208 # will generate a few walking characters
ferencd@0 209 def generate_walkers(grid, max_nr)
ferencd@0 210 walkers = []
ferencd@0 211 return walkers if max_nr == 0
ferencd@0 212
ferencd@0 213 walker_count = rand(1 .. max_nr + 1)
ferencd@0 214 $LOG.info"Generating #{walker_count} walkers\n"
ferencd@0 215 #walkers << {:body => 0, :row => 0, :col => 1, :path => "path_len:3,r:0,c:1,p:'EEE'"}
ferencd@0 216 #walkers << {:body => 0, :row => 0, :col => 4, :path => "path_len:3,r:0,c:4,p:'WWW'"}
ferencd@0 217 dbg_cwa = 0
ferencd@0 218 (1..walker_count).each do
ferencd@0 219 walker_body = dbg_cwa #rand(3)
ferencd@0 220 dbg_cwa = 1 if dbg_cwa == 0
ferencd@0 221 walker_row = rand(1 .. grid.rows - 2) + 1
ferencd@0 222 walker_col = rand(1 .. grid.columns - 2) + 1
ferencd@0 223 walkers <<
ferencd@0 224 {
ferencd@0 225 :body => walker_body,
ferencd@0 226 :row => walker_row,
ferencd@0 227 :col => walker_col,
ferencd@0 228 :path => create_walker_path(grid,walker_row,walker_col)
ferencd@0 229 }
ferencd@0 230 end
ferencd@0 231 walkers
ferencd@0 232 end
ferencd@0 233
ferencd@0 234
ferencd@0 235 #
ferencd@0 236 # Will put the loot stuff on the level. food_locations is an array where wealready have placed food
ferencd@0 237 #
ferencd@0 238 def place_loot(grid, food_locations)
ferencd@0 239 loot_js = 'var loot=['
ferencd@0 240 max_possible_loot = grid.dead_ends[1].length + grid.dead_ends[2].length + grid.dead_ends[4].length + grid.dead_ends[8].length
ferencd@0 241 max_possible_loot = max_possible_loot / 4 + 1
ferencd@0 242
ferencd@0 243 max_loot_count = $ITEMS.length # 32 items defined in the png_to_js
ferencd@0 244
ferencd@0 245 loot_count = [rand(max_possible_loot), max_loot_count].min # We want a lot of loot in the maze
ferencd@0 246
ferencd@0 247 current_loot_count = 0
ferencd@0 248 idxs = [1, 2, 4, 8]
ferencd@0 249 idxsc = 0
ferencd@0 250 current_idx = idxs[idxsc]
ferencd@0 251 loot_js_arr = []
ferencd@0 252 placed_loots = []
ferencd@0 253 current_loot_value = 0
ferencd@0 254
ferencd@0 255 (1..loot_count).each do |_|
ferencd@0 256
ferencd@0 257 place_ctr = 0
ferencd@0 258 can_place = true
ferencd@0 259 begin
ferencd@0 260 cell = grid.dead_ends[current_idx][rand (grid.dead_ends[current_idx].length)]
ferencd@0 261 place_ctr += 1
ferencd@0 262 already_placed_here = placed_loots.select {|loot_loc| loot_loc[:r] == cell.row && loot_loc[:c] == cell.column}
ferencd@0 263 here_is_not_fine = already_placed_here.length > 0
ferencd@0 264
ferencd@0 265 unless here_is_not_fine
ferencd@0 266 food_placed_here = food_locations.select {|food_loc| food_loc[:r] == cell.row && food_loc[:c] == cell.column}
ferencd@0 267 here_is_not_fine |= food_placed_here.length > 0
ferencd@0 268 end
ferencd@0 269
ferencd@0 270 end while here_is_not_fine && place_ctr < grid.dead_ends[current_idx].length-1
ferencd@0 271
ferencd@0 272 if place_ctr == grid.dead_ends[current_idx].length-1
ferencd@0 273 if already_placed_here.length > 0
ferencd@0 274 # try to find the first non empty place in the grid
ferencd@0 275 found = false
ferencd@0 276 (0..grid.dead_ends[current_idx].length - 1).each do |i|
ferencd@0 277 cell = grid.dead_ends[current_idx][i]
ferencd@0 278
ferencd@0 279 already_placed_here = placed_loots.select {|loot_loc| loot_loc[:r] == cell.row && loot_loc[:c] == cell.column}
ferencd@0 280 can_go_here = already_placed_here.length == 0
ferencd@0 281 food_placed_here = food_locations.select {|loot_loc| loot_loc[:r] == cell.row && loot_loc[:c] == cell.column}
ferencd@0 282 can_go_here &= food_placed_here.length == 0
ferencd@0 283
ferencd@0 284 if can_go_here
ferencd@0 285 found = true
ferencd@0 286 break
ferencd@0 287 end
ferencd@0 288
ferencd@0 289 end
ferencd@0 290 can_place = false if found
ferencd@0 291 end
ferencd@0 292 end
ferencd@0 293
ferencd@0 294 if can_place
ferencd@0 295 loot_js_arr << "{r:#{cell.row},c:#{cell.column},t:#{current_loot_count}}" if (cell.row > 0 && cell.column > 0)
ferencd@0 296
ferencd@0 297 current_loot_value += $ITEMS[current_loot_count][:value]
ferencd@0 298 placed_loots << {:r => cell.row, :c => cell.column}
ferencd@0 299 else
ferencd@0 300 break
ferencd@0 301 end
ferencd@0 302
ferencd@0 303 current_idx = idxs[idxsc]
ferencd@0 304
ferencd@0 305 idxsc += 1
ferencd@0 306 if idxsc == idxs.length
ferencd@0 307 idxsc = 0
ferencd@0 308 end
ferencd@0 309 current_loot_count += 1
ferencd@0 310 if current_loot_count == max_loot_count
ferencd@0 311 current_loot_count = 0
ferencd@0 312 end
ferencd@0 313 end
ferencd@0 314 loot_js << loot_js_arr.join(',') << "];\n"
ferencd@0 315 return loot_js, placed_loots, current_loot_value
ferencd@0 316 end
ferencd@0 317
ferencd@0 318 #
ferencd@0 319 # Will put the food on the table ... ie. level
ferencd@0 320 #
ferencd@0 321 def place_food(grid, level_number, game_type)
ferencd@0 322 food_js = 'var food=['
ferencd@0 323 max_possible_food = grid.dead_ends[1].length + grid.dead_ends[2].length + grid.dead_ends[4].length + grid.dead_ends[8].length
ferencd@0 324 max_possible_food = max_possible_food / 4 + 1
ferencd@0 325 food_count = rand(max_possible_food)
ferencd@0 326
ferencd@0 327 if game_type == GAME_TYPE_STORY
ferencd@0 328 if level_number > 55
ferencd@0 329 food_count *= 4
ferencd@0 330 elsif level_number > 45
ferencd@0 331 food_count *= 3
ferencd@0 332 elsif level_number > 20
ferencd@0 333 food_count *= 2
ferencd@0 334 elsif level_number < 10
ferencd@0 335 food_count /= 2
ferencd@0 336 food_count += 1
ferencd@0 337 end
ferencd@0 338 end
ferencd@0 339
ferencd@0 340 max_food_count = 10 # This comes from png_to_js the number of possible food items available
ferencd@0 341 current_food_count = 0 # Counts the food items of the max available
ferencd@0 342 idxs = [1, 2, 4, 8] # The indexes of the cells, which are "dead ends".
ferencd@0 343 idxsc = 0 # Counter for the "dead end" index array
ferencd@0 344 current_idx = idxs[idxsc] # The current "dead end" index
ferencd@0 345 food_js_arr = [] # Will hold the javascript array for the food stuff
ferencd@0 346 placed_foods = [] # Will hold where the food was placed
ferencd@0 347
ferencd@0 348 (1..food_count).each do |_|
ferencd@0 349
ferencd@0 350 place_ctr = 0
ferencd@0 351 can_place = true
ferencd@0 352
ferencd@0 353 # Find a place (cell) where we can place the food item
ferencd@0 354 begin
ferencd@0 355 cell = grid.dead_ends[current_idx][rand (grid.dead_ends[current_idx].length)]
ferencd@0 356 place_ctr += 1
ferencd@0 357 already_placed_here = placed_foods.select {|food_loc| food_loc[:r] == cell.row && food_loc[:c] == cell.column}
ferencd@0 358 end while already_placed_here.length>0 && place_ctr < grid.dead_ends[current_idx].length-1
ferencd@0 359
ferencd@0 360 if place_ctr == grid.dead_ends[current_idx].length-1
ferencd@0 361 # Did not find an "empty dead end" cell to put the food there
ferencd@0 362 if already_placed_here.length > 0
ferencd@0 363 # try to find the first empty cell in the grid
ferencd@0 364 found = false
ferencd@0 365 (0..grid.dead_ends[current_idx].length - 1).each do |i|
ferencd@0 366 cell = grid.dead_ends[current_idx][i]
ferencd@0 367 already_placed_here = placed_foods.select {|food_loc| food_loc[:r] == cell.row && food_loc[:c] == cell.column}
ferencd@0 368 if already_placed_here.length == 0
ferencd@0 369 found = true
ferencd@0 370 break
ferencd@0 371 end
ferencd@0 372 end
ferencd@0 373 can_place = false if found
ferencd@0 374 end
ferencd@0 375 end
ferencd@0 376
ferencd@0 377 if can_place
ferencd@0 378 food_js_arr << "{r:#{cell.row},c:#{cell.column},t:#{current_food_count}}" if (cell.row > 0 && cell.column > 0)
ferencd@0 379 placed_foods << {:r => cell.row, :c => cell.column}
ferencd@0 380 else
ferencd@0 381 break
ferencd@0 382 end
ferencd@0 383
ferencd@0 384 current_idx = idxs[idxsc]
ferencd@0 385
ferencd@0 386 idxsc += 1
ferencd@0 387 if idxsc == idxs.length
ferencd@0 388 idxsc = 0
ferencd@0 389 end
ferencd@0 390 current_food_count += 1
ferencd@0 391 if current_food_count == max_food_count
ferencd@0 392 current_food_count = 0
ferencd@0 393 end
ferencd@0 394 end
ferencd@0 395 food_js << food_js_arr.join(',') << "];\n"
ferencd@0 396 return food_js, placed_foods
ferencd@0 397 end
ferencd@0 398
ferencd@0 399 def place_torches(grid)
ferencd@0 400 torches = grid.identify_torch_locations
ferencd@0 401 torch_data = []
ferencd@0 402 torches.each do |torch|
ferencd@0 403 torch_data << "{i:#{torch[:i]},j:#{torch[:j]},p:#{torch[:number]}}"
ferencd@0 404 end
ferencd@0 405 torch_str = "var torch_placements=[#{torch_data.join(',')}];\n"
ferencd@0 406 storch_str = Utils.split(torch_str, 80)
ferencd@0 407 storch_str
ferencd@0 408 end
ferencd@0 409
ferencd@0 410 def place_doors(grid, exit_row, exit_col)
ferencd@0 411 doors_js = []
ferencd@0 412 # Create a few rooms in the grid
ferencd@0 413 room_count = 0 #rand(4 .. 10)
ferencd@0 414 (1..room_count).each do
ferencd@0 415 room_js = create_room(grid)
ferencd@0 416 if room_js
ferencd@0 417 doors_js << room_js
ferencd@0 418 else
ferencd@0 419 break
ferencd@0 420 end
ferencd@0 421 end
ferencd@0 422 # 0 -> cell with a top door, 1 -> cell with a bottom door in
ferencd@0 423 # adding a door at (0,0) just for visual effects :)
ferencd@0 424 # add another door at (width, height) to indicate exit
ferencd@0 425 doors_js << '{r:0,c:0,v:0}'
ferencd@0 426 doors_js << "{r:#{exit_row},c:#{exit_col},v:5}"
ferencd@0 427 doors_str = "var doors=[ #{doors_js.join(',')}];\nvar exit_row=#{exit_row};\nvar exit_col=#{exit_col};";
ferencd@0 428 sdoors_str = Utils.split(doors_str, 80)
ferencd@0 429 sdoors_str
ferencd@0 430 end
ferencd@0 431
ferencd@0 432 def place_weapons(food_locations, grid, height, width)
ferencd@0 433 weapons_js = 'var weapon_locations = ['
ferencd@0 434 weapon_locs = []
ferencd@0 435 all_weapons = [0, 1, 2, 3] # 0 - spear, armor, shoe, ring
ferencd@0 436 special_weapons = [2, 3].to_set
ferencd@0 437 shoe_distance = 0
ferencd@0 438 ring_distance = 0
ferencd@0 439
ferencd@0 440 all_weapons.each do |weapon|
ferencd@0 441 distance_len = 101
ferencd@0 442 begin
ferencd@0 443 wcol = 1 + rand(width - 2)
ferencd@0 444 wrow = 1+ rand(height - 2)
ferencd@0 445
ferencd@0 446 fl = 0
ferencd@0 447 fp = ''
ferencd@0 448 wep_cell = grid[wrow, wcol]
ferencd@0 449 cell00 = grid[0, 0]
ferencd@0 450 distance_len, _ = distance_calculator(wep_cell, grid, cell00, fl, fp)
ferencd@0 451
ferencd@0 452 if weapon == 2
ferencd@0 453 shoe_distance = distance_len
ferencd@0 454 end
ferencd@0 455
ferencd@0 456 if weapon == 3
ferencd@0 457 ring_distance = distance_len
ferencd@0 458 end
ferencd@0 459
ferencd@0 460 foods_already_placed_here = food_locations.select {|food_loc| food_loc[:r] == wrow && food_loc[:c] == wcol}
ferencd@0 461 food_here = foods_already_placed_here.length > 0
ferencd@0 462
ferencd@0 463 weapons_placed_here = weapon_locs.select {|weapon_loc| weapon_loc[:r] == wrow && weapon_loc[:c] == wcol}
ferencd@0 464 weapon_here = weapons_placed_here.length > 0
ferencd@0 465
ferencd@0 466 end while (distance_len > 101 && special_weapons.include?(weapon)) || food_here || weapon_here
ferencd@0 467
ferencd@0 468 weapon_locs << {:r => wrow, :c => wcol, :t => weapon}
ferencd@0 469 end
ferencd@0 470 weap_tmp_arr=[]
ferencd@0 471 weapon_locs.each {|wpl| weap_tmp_arr << "{r:#{wpl[:r]},c:#{wpl[:c]},t:#{wpl[:t]}}"}
ferencd@0 472 weapons_js << weap_tmp_arr.join(',') << "];\n"
ferencd@0 473 return weapons_js, shoe_distance, ring_distance
ferencd@0 474 end
ferencd@0 475
ferencd@0 476 #
ferencd@0 477 # Will create a new maze with the given number as the seed for the random generator.
ferencd@0 478 #
ferencd@0 479 def handle_maze_with_number(level_number, game_id, main_html='lab.html', game_type = GAME_TYPE_STORY)
ferencd@0 480
ferencd@0 481 current_seed = 0x2ECD06801D ^ level_number
ferencd@0 482 # Set the seed based on the level number
ferencd@0 483 srand(current_seed)
ferencd@0 484
ferencd@0 485 minwidth = 10
ferencd@0 486 minheight = 10
ferencd@0 487
ferencd@0 488 maxwidth = 30
ferencd@0 489 maxheight = 30
ferencd@0 490
ferencd@0 491 # There are no walkers, so the system should be able to handle it
ferencd@0 492 if game_type == GAME_TYPE_MAZE
ferencd@0 493 maxwidth = 40
ferencd@0 494 maxheight = 40
ferencd@0 495 end
ferencd@0 496
ferencd@0 497 maxwidth = [minwidth + level_number, maxwidth].min
ferencd@0 498 maxheight = [minheight + level_number, maxheight].min
ferencd@0 499
ferencd@0 500 width = rand(minwidth .. maxwidth)
ferencd@0 501 height = rand(minheight ..maxheight)
ferencd@0 502
ferencd@0 503 $LOG.info "GameID:#{game_id} Generated: #{width} x #{height} for level #{level_number}"
ferencd@0 504
ferencd@0 505 grid = DistanceGrid.new(height, width)
ferencd@0 506 wilson = Wilsons.new
ferencd@0 507 wilson.on grid
ferencd@0 508
ferencd@0 509 maze_js = ''
ferencd@0 510 maze_js << "history.pushState(null, null, '/#{game_id}');"
ferencd@0 511 maze_js << "var w = #{width};\n"
ferencd@0 512 maze_js << "var h = #{height};\n"
ferencd@0 513 maze_js << "#{grid.maze_js_array}\n"
ferencd@0 514
ferencd@0 515 # Now identify all the places where a torch can go
ferencd@0 516 storch_str = place_torches(grid)
ferencd@0 517 maze_js << storch_str
ferencd@0 518
ferencd@0 519 # Where the exit is for now
ferencd@0 520 exit_col = width - 1
ferencd@0 521 exit_row = height- 1
ferencd@0 522
ferencd@0 523
ferencd@0 524 # At a later stage the exit will not be exactly at the end of the maze, but at the furthest cell from (0,0)
ferencd@0 525 # start = grid[0,0]
ferencd@0 526 # distances = start.distances
ferencd@0 527 # new_start, _ = distances.max
ferencd@0 528 #if game_type == GAME_TYPE_ADVENTURE
ferencd@0 529 # exit_col = new_start.column
ferencd@0 530 # exit_row = new_start.row
ferencd@0 531 #end
ferencd@0 532
ferencd@0 533 # Calculate the shortest path from the entrance to the exit
ferencd@0 534 fp_fl = 0
ferencd@0 535 fp_fp = ''
ferencd@0 536 fp_wep_cell = grid[exit_row, exit_col]
ferencd@0 537 fp_cell00 = grid[0,0]
ferencd@0 538 full_path_len, _ = distance_calculator(fp_wep_cell, grid, fp_cell00, fp_fl, fp_fp)
ferencd@0 539 $LOG.info "Distance from (0,0) to (#{exit_col}, #{exit_row}) is #{full_path_len} steps"
ferencd@0 540
ferencd@0 541 loot_value = 0
ferencd@0 542 walkers = []
ferencd@0 543 food_locations = []
ferencd@0 544
ferencd@0 545 # Create a few rooms with doors. Notes:
ferencd@0 546 # 1. This is not done due to various reasons yet
ferencd@0 547
ferencd@0 548 sdoors_str = place_doors(grid, exit_row, exit_col)
ferencd@0 549 maze_js << sdoors_str
ferencd@0 550
ferencd@0 551 # Create a few walkers but only if the game type is not a simple maze runner
ferencd@0 552 if game_type != GAME_TYPE_MAZE
ferencd@0 553 max_walkers = 0
ferencd@0 554 if game_type == GAME_TYPE_STORY
ferencd@0 555 max_walkers = [ [1, level_number - 3].max, 15].min if level_number > 4
ferencd@0 556 else
ferencd@0 557 max_walkers = rand(3..15)
ferencd@0 558 end
ferencd@0 559
ferencd@0 560 walkers = generate_walkers(grid, max_walkers)
ferencd@0 561 walkers_str_array = []
ferencd@0 562 walkers.each do |walker|
ferencd@0 563 walkers_str_array << "{t:#{walker[:body]},#{walker[:path]}}"
ferencd@0 564 end
ferencd@0 565 walkers_js = "var walkers=[#{walkers_str_array.join(',')}];\n"
ferencd@0 566 maze_js << Utils.split(walkers_js, 80)
ferencd@0 567 maze_js << "var walkers_count = #{walkers.length};"
ferencd@0 568 else
ferencd@0 569 maze_js << "var walkers=[];\nvar walkers_count=0;"
ferencd@0 570 end
ferencd@0 571
ferencd@0 572 # Food items
ferencd@0 573 if game_type != GAME_TYPE_MAZE
ferencd@0 574 food_js, food_locations = place_food(grid, level_number, game_type)
ferencd@0 575 maze_js << "#{food_js}"
ferencd@0 576 else
ferencd@0 577 maze_js << "var food=[];\n"
ferencd@0 578 end
ferencd@0 579
ferencd@0 580 # Loot in the maze only if this is an adventure game
ferencd@0 581 if game_type == GAME_TYPE_ADVENTURE
ferencd@0 582 loot_js, loot_locations, loot_value = place_loot(grid, food_locations)
ferencd@0 583
ferencd@0 584 # Just hack the food locations to contain all the loot
ferencd@0 585 (food_locations << loot_locations).flatten!
ferencd@0 586 else
ferencd@0 587 loot_js = "var loot=[];\n"
ferencd@0 588 end
ferencd@0 589
ferencd@0 590 maze_js << "#{loot_js}"
ferencd@0 591
ferencd@0 592 shoe_distance = 0
ferencd@0 593 ring_distance = 0
ferencd@0 594
ferencd@0 595 # Weapons
ferencd@0 596 if game_type != GAME_TYPE_MAZE
ferencd@0 597 weapons_js,shoe_distance,ring_distance = place_weapons(food_locations, grid, height, width)
ferencd@0 598 maze_js << "#{weapons_js}"
ferencd@0 599 else
ferencd@0 600 maze_js << "var weapon_locations=[];\n"
ferencd@0 601 end
ferencd@0 602
ferencd@0 603 templated_maze = IO.binread(main_html)
ferencd@0 604 if $arrives.has_key? level_number
ferencd@0 605 templates_arrives = $arrives[level_number]
ferencd@0 606 else
ferencd@0 607 templates_arrives = $generic_arrives
ferencd@0 608 end
ferencd@0 609 complete_templates templates_arrives
ferencd@0 610
ferencd@0 611 templates_arrives[:gid] = "#{game_id}"
ferencd@0 612 templates_arrives[:lid] = "#{level_number}"
ferencd@0 613 templates_arrives[:page_loader] = 'upon_page_load();'
ferencd@0 614 # templates_arrives[:page_loader] = 'setup_labyrinth();' if game_type == GAME_TYPE_ADVENTURE or game_type == GAME_TYPE_MAZE
ferencd@0 615
ferencd@0 616 templates_arrives[:sysmenu_visible] = 'hidden;'
ferencd@0 617 templates_arrives[:sysmenu_display] = 'none;'
ferencd@0 618 templates_arrives[:menu_line_visible] = 'visible;'
ferencd@0 619
ferencd@0 620 level_statistics = "Level Number:#{level_number}\nExit at (#{exit_col}, #{exit_row}).\nDistance from (0,0) to (#{exit_col}, #{exit_row}) is #{full_path_len}"
ferencd@0 621
ferencd@0 622 templates_arrives[:badbrowser_visible] = 'hidden;'
ferencd@0 623 templates_arrives[:badbrowser_display] = 'none;'
ferencd@0 624 templates_arrives[:health_visible] = 'visible'
ferencd@0 625 templates_arrives[:statis_visible] = 'hidden'
ferencd@0 626 templates_arrives[:gameid_size] = '105px'
ferencd@0 627 templates_arrives[:gameid_visible] = 'visible'
ferencd@0 628 templates_arrives[:equipment_visible] = 'hidden'
ferencd@0 629 templates_arrives[:messages_visible] = 'hidden; display:none'
ferencd@0 630
ferencd@0 631 if game_type == GAME_TYPE_ADVENTURE
ferencd@0 632 templates_arrives[:gold_visible] = 'visible'
ferencd@0 633 templates_arrives[:skeletons_visible] = 'visible'
ferencd@0 634 templates_arrives[:levelctr_visible] = 'hidden; display:none'
ferencd@0 635 templates_arrives[:timer_visible] = 'visible'
ferencd@0 636 templates_arrives[:day_or_level] = 'Level'
ferencd@0 637 templates_arrives[:new_game_type] = 'new_adventure_game'
ferencd@0 638 level_statistics << "\nGold to be found in this dungeon: #{loot_value}\nSkeletons: #{walkers.length}\nSkeletons' acumen: Droidlike\n"
ferencd@0 639 level_statistics << "Boots of Lightness at #{shoe_distance} steps apart from the start\nRing of Health #{ring_distance} steps apart from the start"
ferencd@0 640
ferencd@0 641 else
ferencd@0 642 templates_arrives[:gold_visible] = 'hidden; display:none'
ferencd@0 643 templates_arrives[:skeletons_visible] = 'hidden; display:none'
ferencd@0 644 templates_arrives[:levelctr_visible] = 'visible'
ferencd@0 645 templates_arrives[:timer_visible] = 'hidden; display:none'
ferencd@0 646 templates_arrives[:new_game_type] = 'new_story_game'
ferencd@0 647 templates_arrives[:day_or_level] = 'Day'
ferencd@0 648
ferencd@0 649 if game_type == GAME_TYPE_MAZE
ferencd@0 650 templates_arrives[:levelctr_visible] = 'hidden; display:none'
ferencd@0 651 templates_arrives[:health_visible] = 'hidden; display:none'
ferencd@0 652 templates_arrives[:equipment_visible] = 'hidden; display:none'
ferencd@0 653 templates_arrives[:timer_visible] = 'visible'
ferencd@0 654 templates_arrives[:statis_visible] = 'visible'
ferencd@0 655 templates_arrives[:new_game_type] = 'new_timechaser_game'
ferencd@0 656 else
ferencd@0 657 templates_arrives[:messages_visible] = 'visible'
ferencd@0 658 end
ferencd@0 659
ferencd@0 660 end
ferencd@0 661
ferencd@0 662 templates_arrives[:story_game_loc] = Game.gen_game_url 'SG'
ferencd@0 663 templates_arrives[:adv_game_loc] = Game.gen_game_url 'AG'
ferencd@0 664 templates_arrives[:timer_game_loc] = Game.gen_game_url 'TG'
ferencd@0 665
ferencd@0 666 templates_arrives[:level_statistics] = level_statistics
ferencd@0 667
ferencd@0 668 full_maze = SimpleHtmlRenderer.render(templated_maze, templates_arrives)
ferencd@0 669 full_maze << '<script type="text/javascript">'
ferencd@0 670
ferencd@0 671
ferencd@0 672 gt = case game_type
ferencd@0 673 when GAME_TYPE_ADVENTURE then 'GAME_TYPE_ADVENTURE'
ferencd@0 674 when GAME_TYPE_STORY then 'GAME_TYPE_STORY'
ferencd@0 675 when GAME_TYPE_MAZE then 'GAME_TYPE_TIMERUN'
ferencd@0 676 else 'GAME_TYPE_TIMERUN'
ferencd@0 677 end
ferencd@0 678 javascripts = ''
ferencd@0 679
ferencd@0 680 javascripts << "const GAME_TYPE_ADVENTURE = 1;\nconst GAME_TYPE_STORY = 2;\nconst GAME_TYPE_TIMERUN = 3\n;var game_type=#{gt};\n"
ferencd@0 681
ferencd@0 682
ferencd@0 683 javascripts << maze_js
ferencd@0 684
ferencd@0 685 if game_type == GAME_TYPE_ADVENTURE
ferencd@0 686 templates_descends = $adventure_descend_summary
ferencd@0 687 else
ferencd@0 688 if game_type == GAME_TYPE_STORY
ferencd@0 689 if $descends.has_key? level_number
ferencd@0 690 templates_descends = $descends[level_number]
ferencd@0 691 else
ferencd@0 692 templates_descends = $generic_descends
ferencd@0 693 end
ferencd@0 694 else
ferencd@0 695 templates_descends = $timerun_descends
ferencd@0 696 end
ferencd@0 697 end
ferencd@0 698
ferencd@0 699 javascripts << "var story_text =\"#{templates_descends[:story_text]}\";\n"
ferencd@0 700 javascripts << "var leave_text =\"#{templates_descends[:leave_text]}\";\n"
ferencd@0 701
ferencd@0 702
ferencd@0 703 javascripts << '</script>'
ferencd@0 704
ferencd@0 705 # javascripts << '<script type="text/javascript" src="graphics.js"></script>'
ferencd@0 706 #
ferencd@0 707 # javascripts << '<script type="text/javascript" src="game.js"></script>'
ferencd@0 708
ferencd@0 709 # templated_game_js = IO.binread 'game.js'
ferencd@0 710 # game_js = SimpleHtmlRenderer.render(templated_game_js, templates_descends)
ferencd@0 711
ferencd@0 712 # javascripts << game_js
ferencd@0 713
ferencd@0 714 full_maze << javascripts
ferencd@0 715
ferencd@0 716 full_maze << 'request("graphics.js");</script></body></html>'
ferencd@0 717 retv = [full_maze]
ferencd@0 718
ferencd@0 719 return 200, retv
ferencd@0 720 end
ferencd@0 721
ferencd@0 722 #
ferencd@0 723 # Handles the root request. Creates a new maze number
ferencd@0 724 #
ferencd@0 725 def handle_root
ferencd@0 726 templated_maze = IO.binread(MAIN_HTML)
ferencd@0 727 templates_arrives = $generic_arrives
ferencd@0 728 complete_templates templates_arrives
ferencd@0 729
ferencd@0 730 templates_arrives[:gid] = ''
ferencd@0 731 templates_arrives[:lid] = ''
ferencd@0 732
ferencd@0 733 templates_arrives[:page_loader] = 'page_loader();'
ferencd@0 734
ferencd@0 735 templates_arrives[:sysmenu_visible] = 'visible;'
ferencd@0 736 templates_arrives[:sysmenu_display] = 'block;'
ferencd@0 737 templates_arrives[:menu_line_visible] = 'hidden;'
ferencd@0 738
ferencd@0 739 templates_arrives[:gameid_visible] = 'hidden'
ferencd@0 740 templates_arrives[:gold_visible] = 'hidden'
ferencd@0 741 templates_arrives[:skeletons_visible] = 'hidden'
ferencd@0 742 templates_arrives[:levelctr_visible] = 'hidden'
ferencd@0 743 templates_arrives[:health_visible] = 'hidden'
ferencd@0 744 templates_arrives[:timer_visible] = 'hidden'
ferencd@0 745 templates_arrives[:gameid_size] = '0px; padding:0'
ferencd@0 746 templates_arrives[:day_or_level] = 'Stage'
ferencd@0 747 templates_arrives[:level_statistics] = 'Nothing here yet'
ferencd@0 748 templates_arrives[:statis_visible] = 'hidden'
ferencd@0 749 templates_arrives[:equipment_visible] = 'hidden'
ferencd@0 750 templates_arrives[:new_game_type] = 'page_loader'
ferencd@0 751 templates_arrives[:messages_visible] = 'hidden'
ferencd@0 752
ferencd@0 753 templates_arrives[:badbrowser_visible] = 'hidden;'
ferencd@0 754 templates_arrives[:badbrowser_display] = 'none;'
ferencd@0 755
ferencd@0 756 templates_arrives[:story_game_loc] = Game.gen_game_url 'SG'
ferencd@0 757 templates_arrives[:adv_game_loc] = Game.gen_game_url 'AG'
ferencd@0 758 templates_arrives[:timer_game_loc] = Game.gen_game_url 'TG'
ferencd@0 759
ferencd@0 760 full_maze = SimpleHtmlRenderer.render(templated_maze, templates_arrives)
ferencd@0 761 full_maze << '</body></html>'
ferencd@0 762
ferencd@0 763 retv = [full_maze]
ferencd@0 764
ferencd@0 765 return 200, retv
ferencd@0 766 end
ferencd@0 767
ferencd@0 768 #
ferencd@0 769 # Delivers a file to the browser
ferencd@0 770 #
ferencd@0 771 def deliver_file(name)
ferencd@0 772
ferencd@0 773 code, content_type, retv = page_not_found
ferencd@0 774
ferencd@0 775 # Will read a file. Supposed to be on the same level as the script
ferencd@0 776 filename = name[1..-1]
ferencd@0 777 if File.exists?(filename)
ferencd@0 778
ferencd@0 779 extension = filename.partition('.').last
ferencd@0 780
ferencd@0 781 code = 200
ferencd@0 782 content_type = { CONTENT_TYPE => CONTENT_TYPES[extension] }
ferencd@0 783 retv = [ IO.binread(name.partition('/').last) ]
ferencd@0 784
ferencd@0 785 end
ferencd@0 786
ferencd@0 787 return code, content_type, retv
ferencd@0 788
ferencd@0 789 end
ferencd@0 790
ferencd@0 791 def handle_game_loader(loc)
ferencd@0 792
ferencd@0 793 begin
ferencd@0 794 key = loc[0..5]
ferencd@0 795 enced = loc[6..-1]
ferencd@0 796 deced = enced.decrypt key
ferencd@0 797 rescue
ferencd@0 798 deced = 'SG'
ferencd@0 799 end
ferencd@0 800 game_type = case deced
ferencd@0 801 when 'SG' then
ferencd@0 802 deced
ferencd@0 803 when 'AG' then
ferencd@0 804 deced
ferencd@0 805 when 'TG' then
ferencd@0 806 deced
ferencd@0 807 else
ferencd@0 808 'SG'
ferencd@0 809 end
ferencd@0 810 code, retv = Game.create_game game_type
ferencd@0 811 return code, retv
ferencd@0 812 end
ferencd@0 813
ferencd@0 814 def retrieve_game_id(request)
ferencd@0 815 gid = ''
ferencd@0 816 gid = request[:headers]['X-Extra'] if request[:headers].has_key? 'X-Extra'
ferencd@0 817 gid = gid.split('=')[1] if gid && gid.count('=') > 0
ferencd@0 818 gid
ferencd@0 819 end
ferencd@0 820
ferencd@0 821 def handle_next_level(gid)
ferencd@0 822
ferencd@0 823 # See where the player was
ferencd@0 824 level_nr = Database.get_db_level_for_gid gid
ferencd@0 825
ferencd@0 826 # Move him to the next level
ferencd@0 827 level_nr += 1
ferencd@0 828 Database.update_level_number_for_gid(level_nr, gid)
ferencd@0 829
ferencd@0 830 # And let's play that level
ferencd@0 831 code, retv = handle_maze_with_number(level_nr, gid, MAIN_HTML, GAME_TYPE_STORY)
ferencd@0 832
ferencd@0 833 return code, retv
ferencd@0 834 end
ferencd@0 835
ferencd@0 836 #
ferencd@0 837 # Each connection will get in here
ferencd@0 838 #
ferencd@0 839 Net::HTTP::Server.run(:port => 8180) do |request, _|
ferencd@0 840 begin
ferencd@0 841 url = request[:uri]
ferencd@0 842
ferencd@0 843 code, content_type, retv = page_not_found
ferencd@0 844
ferencd@0 845 lab_id = url[:path]
ferencd@0 846
ferencd@0 847 # pp lab_id
ferencd@0 848
ferencd@0 849 # what we are trying to load
ferencd@0 850 what = lab_id.to_s
ferencd@0 851
ferencd@0 852 # Handle favico
ferencd@0 853 if what == '/favicon.ico'
ferencd@0 854 code = 200
ferencd@0 855 content_type = {CONTENT_TYPE => 'image/x-icon'}
ferencd@0 856 retv = [ IO.binread('favicon.ico') ]
ferencd@0 857 end
ferencd@0 858
ferencd@0 859 # Nothing, just load the default, and create a new maze
ferencd@0 860 if what == '/'
ferencd@0 861 code, retv = handle_root
ferencd@0 862 elsif what != '/favicon.ico'
ferencd@0 863 # some file that needs to be delivered?
ferencd@0 864 if what.end_with?('.js') or what.end_with?('.png') or what.end_with?('.gif')
ferencd@0 865 code, content_type, retv = deliver_file(what)
ferencd@0 866 else
ferencd@0 867 # The actual location
ferencd@0 868 loc = what[1..-1]
ferencd@0 869
ferencd@0 870 # See if this is a gametype loader from the main screen, ie full hex string:
ferencd@0 871 if loc =~ /^[0-9A-F]+$/i and not loc.is_i?
ferencd@0 872 code, retv = handle_game_loader(loc)
ferencd@0 873 else
ferencd@0 874 # gid will be the Game ID of the current game if story mode, otherwise the adventure mode is selected for the
ferencd@0 875 # current maze
ferencd@0 876 gid = retrieve_game_id(request)
ferencd@0 877
ferencd@0 878 # Crazy locations will lead to random maze games
ferencd@0 879 if loc.length > 9
ferencd@0 880 loc = 'T' + Utils.random_string(8)
ferencd@0 881 end
ferencd@0 882 if gid == loc
ferencd@0 883 # The player advanced to the next level from the game, we have a valid gid equaling the location of the window
ferencd@0 884 code, retv = handle_next_level(gid)
ferencd@0 885 elsif loc.is_i?
ferencd@0 886 # We get here by a simple "GET" from the browser URL, someone just wants to play a maze game
ferencd@0 887 # Now update in the DB the level number for gid
ferencd@0 888 code, retv = handle_maze_with_number(loc.to_i, gid, MAIN_HTML, GAME_TYPE_MAZE)
ferencd@0 889 elsif loc.length > 0 and (loc[0] == 'S' or loc[0] == 'A' or loc[0] == 'T')
ferencd@0 890 level_number = Database.get_db_level_for_gid(loc)
ferencd@0 891 if level_number == -1
ferencd@0 892 level_number = Zlib::crc32(loc, 0)
ferencd@0 893 end
ferencd@0 894 code, retv = handle_maze_with_number(level_number, loc,
ferencd@0 895 MAIN_HTML,
ferencd@0 896 if loc[0] == 'S' then GAME_TYPE_STORY elsif loc[0] == 'T' then GAME_TYPE_MAZE
ferencd@0 897 else GAME_TYPE_ADVENTURE end
ferencd@0 898 )
ferencd@0 899
ferencd@0 900 end
ferencd@0 901 end
ferencd@0 902 end
ferencd@0 903 end
ferencd@0 904 rescue => ex
ferencd@0 905 $LOG.fatal("A Somewhat critical error: #{ex.to_s}")
ferencd@0 906 code, content_type, retv = internal_server_error
ferencd@0 907 raise
ferencd@0 908 end
ferencd@0 909
ferencd@0 910 # This will go back to the caller
ferencd@0 911 reply = [code, content_type, retv]
ferencd@0 912 reply
ferencd@0 913
ferencd@0 914 end
ferencd@0 915