diff options
| author | Mike Buland <Mike.Buland@mjfirm.com> | 2016-10-24 15:05:46 -0600 |
|---|---|---|
| committer | Mike Buland <Mike.Buland@mjfirm.com> | 2016-10-24 15:05:46 -0600 |
| commit | c8b8770f49883f94678f215b98ece3d67f8aa9ef (patch) | |
| tree | 673879b7d8615f3f298f55a48f791e787eb81dc1 /js | |
| parent | 39d593054de993469809170ebf53a231caac92fb (diff) | |
| download | lost-c8b8770f49883f94678f215b98ece3d67f8aa9ef.tar.gz lost-c8b8770f49883f94678f215b98ece3d67f8aa9ef.tar.bz2 lost-c8b8770f49883f94678f215b98ece3d67f8aa9ef.tar.xz lost-c8b8770f49883f94678f215b98ece3d67f8aa9ef.zip | |
Mainly just lots and lots of comments.
Diffstat (limited to 'js')
| -rw-r--r-- | js/lost.html | 4 | ||||
| -rw-r--r-- | js/lost.js | 305 |
2 files changed, 248 insertions, 61 deletions
diff --git a/js/lost.html b/js/lost.html index d4443ab..c634ba2 100644 --- a/js/lost.html +++ b/js/lost.html | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | <div id="maze-box"></div> | 10 | <div id="maze-box"></div> |
| 11 | <div id="button-box"></div> | 11 | <div id="button-box"></div> |
| 12 | </div> | 12 | </div> |
| 13 | <div id="floor-box"></div> | 13 | <div id="floor-box"></div> |
| 14 | <div class="float-clear"></div> | 14 | <div class="float-clear"></div> |
| 15 | <div id="editor-cont"> | 15 | <div id="editor-cont"> |
| 16 | <p>Provide a number below for each dimension in the new maze. The numbers are the size of each dimension. Entering "10, 3" will create a maze that is 10 wide and 3 tall. Entering "5, 5, 5, 5, 5" will create a 5 dimensional maze, every dimension will be 5 wide.</p> | 16 | <p>Provide a number below for each dimension in the new maze. The numbers are the size of each dimension. Entering "10, 3" will create a maze that is 10 wide and 3 tall. Entering "5, 5, 5, 5, 5" will create a 5 dimensional maze, every dimension will be 5 wide.</p> |
| @@ -21,7 +21,7 @@ | |||
| 21 | <script type="text/javascript"> | 21 | <script type="text/javascript"> |
| 22 | lostInit({ | 22 | lostInit({ |
| 23 | 'render': { | 23 | 'render': { |
| 24 | 'name': 'RenderCanvas2D', | 24 | 'name': RenderCanvas2D, |
| 25 | 'params': { | 25 | 'params': { |
| 26 | 'maze': 'maze-box', | 26 | 'maze': 'maze-box', |
| 27 | 'buttons': 'button-box', | 27 | 'buttons': 'button-box', |
| @@ -5,27 +5,94 @@ | |||
| 5 | // Helper functions | 5 | // Helper functions |
| 6 | // | 6 | // |
| 7 | 7 | ||
| 8 | // Return the id for the opposite direction of the direction given. | ||
| 9 | function oppositeDir( iDir ) | ||
| 10 | { | ||
| 11 | if( iDir%2 === 1 ) | ||
| 12 | return iDir-1; | ||
| 13 | return iDir+1; | ||
| 14 | } | ||
| 15 | |||
| 16 | // | ||
| 17 | // Create a new button element using the dom that has the given label, | ||
| 18 | // and calls the movePlayer method on the given map, along the provided | ||
| 19 | // dimension, and in the specified direction. | ||
| 20 | // | ||
| 21 | // The button created is then returned. | ||
| 22 | // | ||
| 23 | function createMoveButton( rMap, iDim, iDir, sLabel ) | ||
| 24 | { | ||
| 25 | let btn = document.createElement('button'); | ||
| 26 | btn.addEventListener( | ||
| 27 | 'click', | ||
| 28 | Map.prototype.movePlayer.bind( | ||
| 29 | rMap, | ||
| 30 | iDim, | ||
| 31 | iDir | ||
| 32 | ) | ||
| 33 | ); | ||
| 34 | if( sLabel === null || sLabel === '' ) | ||
| 35 | { | ||
| 36 | btn.appendChild( | ||
| 37 | document.createTextNode('Dim ' + (j+1) + ': -') | ||
| 38 | ); | ||
| 39 | } | ||
| 40 | else | ||
| 41 | { | ||
| 42 | btn.appendChild( | ||
| 43 | document.createTextNode( sLabel ) | ||
| 44 | ); | ||
| 45 | } | ||
| 46 | return btn; | ||
| 47 | } | ||
| 48 | |||
| 8 | // | 49 | // |
| 9 | // Class: RandomLcg | 50 | // Class: RandomLcg |
| 10 | // | 51 | // |
| 52 | // This implements a Linear Congruential Generator PRNG. Is this the best PRNG? | ||
| 53 | // Nope! Is it decently random for our purposes, sure. | ||
| 54 | // | ||
| 55 | // Why was this implemented? I wanted to be able to share mazes, in order to do | ||
| 56 | // that we needed two things that we can't get from JS by default: | ||
| 57 | // 1. To be able to set the seed (and get it if possible). | ||
| 58 | // 2. To be sure that the same algorithm would be used on every version of | ||
| 59 | // every browser. | ||
| 60 | // | ||
| 61 | // Unfortunately JavaScript doesn't gurantee either of these things, so instead | ||
| 62 | // of writing a CMWC and using a bunch of memory I just used the settings from | ||
| 63 | // the glibc random function and here we are. | ||
| 64 | // | ||
| 11 | function RandomLcg() | 65 | function RandomLcg() |
| 12 | { | 66 | { |
| 67 | // Current state | ||
| 13 | this.iState = 0; | 68 | this.iState = 0; |
| 69 | |||
| 70 | // Initial seed, remember this so we can display it easily later. | ||
| 14 | this.iSeed = 0; | 71 | this.iSeed = 0; |
| 15 | 72 | ||
| 73 | // Set the seed randomly to start. | ||
| 16 | this.inventSeed(); | 74 | this.inventSeed(); |
| 17 | } | 75 | } |
| 18 | 76 | ||
| 77 | // | ||
| 78 | // Replace the current seed and reset the state. | ||
| 79 | // | ||
| 19 | RandomLcg.prototype.setSeed = function setSeed( iSeed ) | 80 | RandomLcg.prototype.setSeed = function setSeed( iSeed ) |
| 20 | { | 81 | { |
| 21 | this.iState = this.iSeed = iSeed; | 82 | this.iState = this.iSeed = iSeed; |
| 22 | } | 83 | } |
| 23 | 84 | ||
| 85 | // | ||
| 86 | // Get the seed that was initially used on this random number generator. | ||
| 87 | // | ||
| 24 | RandomLcg.prototype.getSeed = function getSeed() | 88 | RandomLcg.prototype.getSeed = function getSeed() |
| 25 | { | 89 | { |
| 26 | return this.iSeed; | 90 | return this.iSeed; |
| 27 | } | 91 | } |
| 28 | 92 | ||
| 93 | // | ||
| 94 | // Make up a seed. | ||
| 95 | // | ||
| 29 | RandomLcg.prototype.inventSeed = function inventSeed() | 96 | RandomLcg.prototype.inventSeed = function inventSeed() |
| 30 | { | 97 | { |
| 31 | // Based on my reading it's safest to assume that we can get 16 bits worth | 98 | // Based on my reading it's safest to assume that we can get 16 bits worth |
| @@ -37,48 +104,52 @@ RandomLcg.prototype.inventSeed = function inventSeed() | |||
| 37 | ); | 104 | ); |
| 38 | } | 105 | } |
| 39 | 106 | ||
| 107 | // | ||
| 108 | // Get us a random number between 0 and 1.0, exclusive of the upper bound. | ||
| 109 | // | ||
| 40 | RandomLcg.prototype.random = function random() | 110 | RandomLcg.prototype.random = function random() |
| 41 | { | 111 | { |
| 42 | this.iState = ((this.iState * 1103515245) + 12345) & 0x7fffffff; | 112 | this.iState = ((this.iState * 1103515245) + 12345) & 0x7fffffff; |
| 43 | return this.iState/(0x7fffffff+1); | 113 | return this.iState/(0x7fffffff+1); |
| 44 | } | 114 | } |
| 45 | 115 | ||
| 116 | // | ||
| 117 | // Get us a random integer between 0 and max, exclusive of the upper bound. | ||
| 118 | // | ||
| 46 | RandomLcg.prototype.randInt = function randInt( max ) | 119 | RandomLcg.prototype.randInt = function randInt( max ) |
| 47 | { | 120 | { |
| 48 | return Math.floor(this.random() * max); | 121 | return Math.floor(this.random() * max); |
| 49 | } | 122 | } |
| 50 | 123 | ||
| 124 | // Lets just build a shared prng object to use all over. | ||
| 51 | let lRand = new RandomLcg(); | 125 | let lRand = new RandomLcg(); |
| 52 | 126 | ||
| 53 | // Just return a random integer between 0 and max, exclusive on the upper bound. | ||
| 54 | function randInt( max ) | ||
| 55 | { | ||
| 56 | return lRand.randInt( max ); | ||
| 57 | // return Math.floor(Math.random() * max); | ||
| 58 | } | ||
| 59 | |||
| 60 | // Return the id for the opposite direction of the direction given. | ||
| 61 | function oppositeDir( iDir ) | ||
| 62 | { | ||
| 63 | if( iDir%2 === 1 ) | ||
| 64 | return iDir-1; | ||
| 65 | return iDir+1; | ||
| 66 | } | ||
| 67 | |||
| 68 | // | 127 | // |
| 69 | // Class: Signal | 128 | // Class: Signal |
| 70 | // | 129 | // |
| 130 | // Super simple implementation of a signal/slot concept. I didn't need most of | ||
| 131 | // the features, so this just lets us connect 0-parameter functions and call | ||
| 132 | // them en-masse whenever the signal is fired. | ||
| 133 | // | ||
| 71 | function Signal() | 134 | function Signal() |
| 72 | { | 135 | { |
| 73 | this.aSlot = new Array(); | 136 | this.aSlot = new Array(); |
| 74 | } | 137 | } |
| 75 | 138 | ||
| 139 | // | ||
| 140 | // Connect this signal to a new slot (function to call). I recommend using bind | ||
| 141 | // to create valid object-function references that have state supposed to | ||
| 142 | // disembodied functions. | ||
| 143 | // | ||
| 76 | Signal.prototype.connect = function connect( fSlot ) | 144 | Signal.prototype.connect = function connect( fSlot ) |
| 77 | { | 145 | { |
| 78 | this.aSlot.push( fSlot ); | 146 | this.aSlot.push( fSlot ); |
| 79 | } | 147 | } |
| 80 | 148 | ||
| 81 | Signal.prototype.call = function call() | 149 | // |
| 150 | // Trigger the signal, and notify all slots bound to this signal. | ||
| 151 | // | ||
| 152 | Signal.prototype.emit = function emit() | ||
| 82 | { | 153 | { |
| 83 | for( let j = 0; j < this.aSlot.length; j++ ) | 154 | for( let j = 0; j < this.aSlot.length; j++ ) |
| 84 | { | 155 | { |
| @@ -89,6 +160,8 @@ Signal.prototype.call = function call() | |||
| 89 | // | 160 | // |
| 90 | // Class: Cell | 161 | // Class: Cell |
| 91 | // | 162 | // |
| 163 | // Simple container that tracks info about a cell in the maze. | ||
| 164 | // | ||
| 92 | function Cell() | 165 | function Cell() |
| 93 | { | 166 | { |
| 94 | this.iDist = 0; | 167 | this.iDist = 0; |
| @@ -99,6 +172,9 @@ function Cell() | |||
| 99 | // | 172 | // |
| 100 | // Class: Position | 173 | // Class: Position |
| 101 | // | 174 | // |
| 175 | // A simple class that keeps track of coordinates in N-dimensional space. | ||
| 176 | // That's really just an array of numbers with N spaces in it. | ||
| 177 | // | ||
| 102 | function Position( iDims, ...Vals ) | 178 | function Position( iDims, ...Vals ) |
| 103 | { | 179 | { |
| 104 | if( typeof iDims === 'string' ) | 180 | if( typeof iDims === 'string' ) |
| @@ -146,27 +222,45 @@ function Position( iDims, ...Vals ) | |||
| 146 | } | 222 | } |
| 147 | } | 223 | } |
| 148 | 224 | ||
| 225 | // | ||
| 226 | // Get the number of dimensions defined in this position. | ||
| 227 | // | ||
| 149 | Position.prototype.getDims = function getDims() | 228 | Position.prototype.getDims = function getDims() |
| 150 | { | 229 | { |
| 151 | return this.iDims; | 230 | return this.iDims; |
| 152 | } | 231 | } |
| 153 | 232 | ||
| 233 | // | ||
| 234 | // Get the value of one dimension of the coordinate in this position. | ||
| 235 | // | ||
| 154 | Position.prototype.get = function get( iDim ) | 236 | Position.prototype.get = function get( iDim ) |
| 155 | { | 237 | { |
| 156 | return this.aiValues[iDim]; | 238 | return this.aiValues[iDim]; |
| 157 | } | 239 | } |
| 158 | 240 | ||
| 241 | // | ||
| 242 | // Set the value of one dimension of the coordinate in this position. | ||
| 243 | // This modifies the position in place. | ||
| 244 | // | ||
| 159 | Position.prototype.set = function set( iDim, iVal ) | 245 | Position.prototype.set = function set( iDim, iVal ) |
| 160 | { | 246 | { |
| 161 | this.aiValues[iDim] = iVal; | 247 | this.aiValues[iDim] = iVal; |
| 162 | } | 248 | } |
| 163 | 249 | ||
| 250 | // | ||
| 251 | // Apply a delta to the specified dimension in the current position. | ||
| 252 | // This modifies the position in place. | ||
| 253 | // | ||
| 164 | Position.prototype.add = function add( iDim, iDelta ) | 254 | Position.prototype.add = function add( iDim, iDelta ) |
| 165 | { | 255 | { |
| 166 | this.aiValues[iDim] += iDelta; | 256 | this.aiValues[iDim] += iDelta; |
| 167 | return this.aiValues[iDim]; | 257 | return this.aiValues[iDim]; |
| 168 | } | 258 | } |
| 169 | 259 | ||
| 260 | // | ||
| 261 | // Copy the current position object and return one with a modified value in | ||
| 262 | // the specified dimension. | ||
| 263 | // | ||
| 170 | Position.prototype.translate = function translate( iDim, iDelta ) | 264 | Position.prototype.translate = function translate( iDim, iDelta ) |
| 171 | { | 265 | { |
| 172 | let tmp = new Position( this.iDims, ...this.aiValues.slice() ); | 266 | let tmp = new Position( this.iDims, ...this.aiValues.slice() ); |
| @@ -174,11 +268,18 @@ Position.prototype.translate = function translate( iDim, iDelta ) | |||
| 174 | return tmp; | 268 | return tmp; |
| 175 | } | 269 | } |
| 176 | 270 | ||
| 271 | // | ||
| 272 | // Return an exact copy of this position object. | ||
| 273 | // | ||
| 177 | Position.prototype.copy = function translate() | 274 | Position.prototype.copy = function translate() |
| 178 | { | 275 | { |
| 179 | return new Position( this.iDims, ...this.aiValues.slice() ); | 276 | return new Position( this.iDims, ...this.aiValues.slice() ); |
| 180 | } | 277 | } |
| 181 | 278 | ||
| 279 | // | ||
| 280 | // Compare two position objects for equality. Return true if they are the same, | ||
| 281 | // false otherwise. | ||
| 282 | // | ||
| 182 | Position.prototype.equals = function equals( rhs ) | 283 | Position.prototype.equals = function equals( rhs ) |
| 183 | { | 284 | { |
| 184 | if( this.iDims != rhs.iDims && | 285 | if( this.iDims != rhs.iDims && |
| @@ -194,6 +295,9 @@ Position.prototype.equals = function equals( rhs ) | |||
| 194 | return true; | 295 | return true; |
| 195 | } | 296 | } |
| 196 | 297 | ||
| 298 | // | ||
| 299 | // Converts the position to a nicely formatted string of numbers. | ||
| 300 | // | ||
| 197 | Position.prototype.toString = function toString() | 301 | Position.prototype.toString = function toString() |
| 198 | { | 302 | { |
| 199 | let ret = this.aiValues[0].toString(); | 303 | let ret = this.aiValues[0].toString(); |
| @@ -208,6 +312,10 @@ Position.prototype.toString = function toString() | |||
| 208 | // | 312 | // |
| 209 | // Class: Map | 313 | // Class: Map |
| 210 | // | 314 | // |
| 315 | // The maze itself. This doesn't do a whole lot on it's own except track data | ||
| 316 | // and manage the player position and worms. The worms do the real work of | ||
| 317 | // generating a maze. | ||
| 318 | // | ||
| 211 | function Map( Dimensions ) | 319 | function Map( Dimensions ) |
| 212 | { | 320 | { |
| 213 | // Store dimensional data | 321 | // Store dimensional data |
| @@ -234,27 +342,44 @@ function Map( Dimensions ) | |||
| 234 | } | 342 | } |
| 235 | } | 343 | } |
| 236 | 344 | ||
| 345 | // | ||
| 346 | // Get the number of dimensions in this maze. | ||
| 347 | // | ||
| 237 | Map.prototype.getDims = function getDims() | 348 | Map.prototype.getDims = function getDims() |
| 238 | { | 349 | { |
| 239 | return this.Dimensions.getDims(); | 350 | return this.Dimensions.getDims(); |
| 240 | } | 351 | } |
| 241 | 352 | ||
| 353 | // | ||
| 354 | // Get the size of the specified dimension. | ||
| 355 | // | ||
| 242 | Map.prototype.getSize = function getSize( iDim ) | 356 | Map.prototype.getSize = function getSize( iDim ) |
| 243 | { | 357 | { |
| 244 | return this.Dimensions.get( iDim ); | 358 | return this.Dimensions.get( iDim ); |
| 245 | } | 359 | } |
| 246 | 360 | ||
| 361 | // | ||
| 362 | // Get a reference to the current player position in the maze. | ||
| 363 | // | ||
| 247 | Map.prototype.getPlayerPos = function getPlayerPos() | 364 | Map.prototype.getPlayerPos = function getPlayerPos() |
| 248 | { | 365 | { |
| 249 | return this.pPlayer; | 366 | return this.pPlayer; |
| 250 | } | 367 | } |
| 251 | 368 | ||
| 369 | // | ||
| 370 | // Replace the player position with a new position. | ||
| 371 | // | ||
| 252 | Map.prototype.setPlayerPos = function setPlayerPos( pNewPos ) | 372 | Map.prototype.setPlayerPos = function setPlayerPos( pNewPos ) |
| 253 | { | 373 | { |
| 254 | this.pPlayer = pNewPos; | 374 | this.pPlayer = pNewPos; |
| 255 | this.ePlayerMoved.call(); | 375 | this.ePlayerMoved.emit(); |
| 256 | } | 376 | } |
| 257 | 377 | ||
| 378 | // | ||
| 379 | // Move the player the specified amount (iDelta) along the specified dimension | ||
| 380 | // (iDim). This takes walls and maze borders into account, and will not move | ||
| 381 | // the player in an "illegal" way. | ||
| 382 | // | ||
| 258 | Map.prototype.movePlayer = function movePlayer( iDim, iDelta ) | 383 | Map.prototype.movePlayer = function movePlayer( iDim, iDelta ) |
| 259 | { | 384 | { |
| 260 | let cCur = this.get( this.pPlayer ); | 385 | let cCur = this.get( this.pPlayer ); |
| @@ -271,14 +396,17 @@ Map.prototype.movePlayer = function movePlayer( iDim, iDelta ) | |||
| 271 | return; | 396 | return; |
| 272 | 397 | ||
| 273 | this.pPlayer.add( iDim, iDelta ); | 398 | this.pPlayer.add( iDim, iDelta ); |
| 274 | this.ePlayerMoved.call(); | 399 | this.ePlayerMoved.emit(); |
| 275 | 400 | ||
| 276 | if( this.pPlayer.equals( this.pGoal ) ) | 401 | if( this.pPlayer.equals( this.pGoal ) ) |
| 277 | { | 402 | { |
| 278 | this.eVictory.call(); | 403 | this.eVictory.emit(); |
| 279 | } | 404 | } |
| 280 | } | 405 | } |
| 281 | 406 | ||
| 407 | // | ||
| 408 | // Helper that determines if the provided position is inside the maze or not. | ||
| 409 | // | ||
| 282 | Map.prototype.isInside = function isInside( Position ) | 410 | Map.prototype.isInside = function isInside( Position ) |
| 283 | { | 411 | { |
| 284 | if( Position.getDims() != this.Dimensions.getDims() ) | 412 | if( Position.getDims() != this.Dimensions.getDims() ) |
| @@ -299,6 +427,12 @@ Map.prototype.isInside = function isInside( Position ) | |||
| 299 | return true; | 427 | return true; |
| 300 | } | 428 | } |
| 301 | 429 | ||
| 430 | // | ||
| 431 | // Internal helper function. This converts from a Position object to an array | ||
| 432 | // index, effectively flattening an arbitrarily dimensional coordinate into a | ||
| 433 | // one dimensional array coordinate. This is used to find the actual storage | ||
| 434 | // location of cells internally. | ||
| 435 | // | ||
| 302 | Map.prototype.getIndex = function getIndex( Position ) | 436 | Map.prototype.getIndex = function getIndex( Position ) |
| 303 | { | 437 | { |
| 304 | if( !this.isInside( Position ) ) | 438 | if( !this.isInside( Position ) ) |
| @@ -316,17 +450,25 @@ Map.prototype.getIndex = function getIndex( Position ) | |||
| 316 | return iIdx; | 450 | return iIdx; |
| 317 | } | 451 | } |
| 318 | 452 | ||
| 453 | // | ||
| 454 | // Get a cell at the given Position. | ||
| 455 | // | ||
| 319 | Map.prototype.get = function get( Position ) | 456 | Map.prototype.get = function get( Position ) |
| 320 | { | 457 | { |
| 321 | return this.aCell[this.getIndex( Position )]; | 458 | return this.aCell[this.getIndex( Position )]; |
| 322 | } | 459 | } |
| 323 | 460 | ||
| 461 | // | ||
| 462 | // Create a new worm and add it to the maze. Specify the starting position | ||
| 463 | // and the loop chance (betweer 0.0 and 1.0). This returns the ID that the | ||
| 464 | // added worm was assigned, which starts at one and goes up from there. | ||
| 465 | // | ||
| 324 | Map.prototype.addWorm = function addWorm( pStart, dLoopChance ) | 466 | Map.prototype.addWorm = function addWorm( pStart, dLoopChance ) |
| 325 | { | 467 | { |
| 326 | if( this.pPlayer === null ) | 468 | if( this.pPlayer === null ) |
| 327 | { | 469 | { |
| 328 | this.pPlayer = pStart; | 470 | this.pPlayer = pStart; |
| 329 | this.ePlayerSetup.call(); | 471 | this.ePlayerSetup.emit(); |
| 330 | } | 472 | } |
| 331 | else if( this.pGoal === null ) | 473 | else if( this.pGoal === null ) |
| 332 | { | 474 | { |
| @@ -344,6 +486,13 @@ Map.prototype.addWorm = function addWorm( pStart, dLoopChance ) | |||
| 344 | return iNewId; | 486 | return iNewId; |
| 345 | } | 487 | } |
| 346 | 488 | ||
| 489 | // | ||
| 490 | // Worker function. This calls the timestep funcion on each worm until they | ||
| 491 | // report that they are done working and have exhausted all possible moves. | ||
| 492 | // | ||
| 493 | // At the moment this function assumes we have 2 worms and connects them | ||
| 494 | // automatically once it's done running. | ||
| 495 | // | ||
| 347 | Map.prototype.buildMaze = function buildMaze() | 496 | Map.prototype.buildMaze = function buildMaze() |
| 348 | { | 497 | { |
| 349 | do | 498 | do |
| @@ -360,6 +509,13 @@ Map.prototype.buildMaze = function buildMaze() | |||
| 360 | this.connect( 1, 2 ); | 509 | this.connect( 1, 2 ); |
| 361 | } | 510 | } |
| 362 | 511 | ||
| 512 | // | ||
| 513 | // Connect the pathways created by two worms, specified by iWormId1 and | ||
| 514 | // iWormId2 to each other. this searches all walls in the maze and looks for | ||
| 515 | // a wall that seperates the path created by these two worms, then finds the | ||
| 516 | // wall that seperates the longest combined pathway between the two, and opens | ||
| 517 | // it up into a pathway. | ||
| 518 | // | ||
| 363 | Map.prototype.connect = function connect( iWormId1, iWormId2 ) | 519 | Map.prototype.connect = function connect( iWormId1, iWormId2 ) |
| 364 | { | 520 | { |
| 365 | let p = new Position( this.getDims() ); | 521 | let p = new Position( this.getDims() ); |
| @@ -432,6 +588,8 @@ Map.prototype.connect = function connect( iWormId1, iWormId2 ) | |||
| 432 | // | 588 | // |
| 433 | // Class: Vector | 589 | // Class: Vector |
| 434 | // | 590 | // |
| 591 | // Simple helper class that stores a position and direction. | ||
| 592 | // | ||
| 435 | function Vector( pPos, iDir ) | 593 | function Vector( pPos, iDir ) |
| 436 | { | 594 | { |
| 437 | this.pPos = pPos; | 595 | this.pPos = pPos; |
| @@ -441,6 +599,26 @@ function Vector( pPos, iDir ) | |||
| 441 | // | 599 | // |
| 442 | // Class: Worm | 600 | // Class: Worm |
| 443 | // | 601 | // |
| 602 | // The main workhorse (workworm?) of maze generation. The worm "eats" a path | ||
| 603 | // through the maze. The basic algorithm works as follows: | ||
| 604 | // 1. Search all directions around the current cell and list all unvisited | ||
| 605 | // cells, the previous cell that we came from, and all cells that we | ||
| 606 | // created but are seperated from our current position by a wall. | ||
| 607 | // 2. If there are open cells, then we'll select one at random and travel to | ||
| 608 | // it, but first: | ||
| 609 | // 2.a. If there are adjacent cells that we created, generate a random | ||
| 610 | // number and compare it to the loop threshold. If it's smaller, | ||
| 611 | // then select an adjacent room at random and break through the wall | ||
| 612 | // to that room. | ||
| 613 | // 3. If there are not open cells then travel back to the previous cell that | ||
| 614 | // we came from. Start over from #1 in this cell. | ||
| 615 | // 4. If we reach the starting position again, then bailout and consider our | ||
| 616 | // work done. | ||
| 617 | // Every cell that a worm visits is marked with the worm's id (1 or greater), | ||
| 618 | // and a distance value that increases by one for each cell away from the start | ||
| 619 | // that we've traveled. When backtracking we update our current distance so | ||
| 620 | // that all distances are contiguous and increasing away from start. | ||
| 621 | // | ||
| 444 | function Worm( iId, pStart, rMap, dLoopChance ) | 622 | function Worm( iId, pStart, rMap, dLoopChance ) |
| 445 | { | 623 | { |
| 446 | // Initialize basic state, we start with distance set to 1 | 624 | // Initialize basic state, we start with distance set to 1 |
| @@ -470,11 +648,14 @@ function Worm( iId, pStart, rMap, dLoopChance ) | |||
| 470 | if( iDirs.length > 0 ) | 648 | if( iDirs.length > 0 ) |
| 471 | { | 649 | { |
| 472 | // We are near a wall, pick a random wall to open a hole in | 650 | // We are near a wall, pick a random wall to open a hole in |
| 473 | this.rMap.get(this.pPosition).iWalls |= iDirs[randInt(iDirs.length)]; | 651 | this.rMap.get(this.pPosition).iWalls |= iDirs[lRand.randInt(iDirs.length)]; |
| 474 | this.rMap.get(this.pPosition).iPath = this.iId; | 652 | this.rMap.get(this.pPosition).iPath = this.iId; |
| 475 | } | 653 | } |
| 476 | } | 654 | } |
| 477 | 655 | ||
| 656 | // | ||
| 657 | // Perform one step as descirbed in the constructor. | ||
| 658 | // | ||
| 478 | Worm.prototype.timestep = function timestep() | 659 | Worm.prototype.timestep = function timestep() |
| 479 | { | 660 | { |
| 480 | // Handy to reference how many dimensions we have | 661 | // Handy to reference how many dimensions we have |
| @@ -553,7 +734,7 @@ Worm.prototype.timestep = function timestep() | |||
| 553 | } | 734 | } |
| 554 | 735 | ||
| 555 | cCur = this.rMap.get( this.pPosition ); | 736 | cCur = this.rMap.get( this.pPosition ); |
| 556 | let iSel = randInt( pDirs.length ); | 737 | let iSel = lRand.randInt( pDirs.length ); |
| 557 | cCur.iWalls |= (1<<pDirs[iSel].iDir); | 738 | cCur.iWalls |= (1<<pDirs[iSel].iDir); |
| 558 | let cNext = this.rMap.get( pDirs[iSel].pPos ); | 739 | let cNext = this.rMap.get( pDirs[iSel].pPos ); |
| 559 | cNext.iWalls |= (1<<oppositeDir( pDirs[iSel].iDir )); | 740 | cNext.iWalls |= (1<<oppositeDir( pDirs[iSel].iDir )); |
| @@ -564,7 +745,7 @@ Worm.prototype.timestep = function timestep() | |||
| 564 | 745 | ||
| 565 | if( pLoopDirs.length > 0 && lRand.random() <= this.dLoopChance ) | 746 | if( pLoopDirs.length > 0 && lRand.random() <= this.dLoopChance ) |
| 566 | { | 747 | { |
| 567 | iSel = pLoopDirs[randInt( pLoopDirs.length )]; | 748 | iSel = pLoopDirs[lRand.randInt( pLoopDirs.length )]; |
| 568 | cCur.iWalls |= (1<<iSel.iDir); | 749 | cCur.iWalls |= (1<<iSel.iDir); |
| 569 | cNext = this.rMap.get( iSel.pPos ); | 750 | cNext = this.rMap.get( iSel.pPos ); |
| 570 | cNext.iWalls |= (1<<oppositeDir( iSel.iDir )); | 751 | cNext.iWalls |= (1<<oppositeDir( iSel.iDir )); |
| @@ -576,45 +757,29 @@ Worm.prototype.timestep = function timestep() | |||
| 576 | // | 757 | // |
| 577 | // Class: Render | 758 | // Class: Render |
| 578 | // | 759 | // |
| 760 | // Base class of render classes. Doesn't do anything on it's own. | ||
| 761 | // | ||
| 579 | function Render( rMap, eMazeContainer ) | 762 | function Render( rMap, eMazeContainer ) |
| 580 | { | 763 | { |
| 581 | this.rMap = rMap; | 764 | this.rMap = rMap; |
| 582 | this.eMazeContainer = eMazeContainer; | 765 | this.eMazeContainer = eMazeContainer; |
| 583 | } | 766 | } |
| 584 | 767 | ||
| 768 | // | ||
| 769 | // Empty base class function specifying that there should be a render function | ||
| 770 | // in child classes. | ||
| 771 | // | ||
| 585 | Render.prototype.render = function render() | 772 | Render.prototype.render = function render() |
| 586 | { | 773 | { |
| 587 | } | 774 | } |
| 588 | 775 | ||
| 589 | function createMoveButton( rMap, iDim, iDir, sLabel ) | ||
| 590 | { | ||
| 591 | let btn = document.createElement('button'); | ||
| 592 | btn.addEventListener( | ||
| 593 | 'click', | ||
| 594 | Map.prototype.movePlayer.bind( | ||
| 595 | rMap, | ||
| 596 | iDim, | ||
| 597 | iDir | ||
| 598 | ) | ||
| 599 | ); | ||
| 600 | if( sLabel === null || sLabel === '' ) | ||
| 601 | { | ||
| 602 | btn.appendChild( | ||
| 603 | document.createTextNode('Dim ' + (j+1) + ': -') | ||
| 604 | ); | ||
| 605 | } | ||
| 606 | else | ||
| 607 | { | ||
| 608 | btn.appendChild( | ||
| 609 | document.createTextNode( sLabel ) | ||
| 610 | ); | ||
| 611 | } | ||
| 612 | return btn; | ||
| 613 | } | ||
| 614 | |||
| 615 | // | 776 | // |
| 616 | // Class: RenderCanvas2D | 777 | // Class: RenderCanvas2D |
| 617 | // | 778 | // |
| 779 | // Our main render class. This generates a single 2d slice of the current maze | ||
| 780 | // on the "floor" that the player is currently on. It also generates and | ||
| 781 | // manages buttons you can use to interact with the maze. | ||
| 782 | // | ||
| 618 | function RenderCanvas2D( rMap, params ) // eMazeContainer, eUIContainer ) | 783 | function RenderCanvas2D( rMap, params ) // eMazeContainer, eUIContainer ) |
| 619 | { | 784 | { |
| 620 | let eMazeContainer = document.getElementById(params['maze']); | 785 | let eMazeContainer = document.getElementById(params['maze']); |
| @@ -779,9 +944,20 @@ function RenderCanvas2D( rMap, params ) // eMazeContainer, eUIContainer ) | |||
| 779 | this.updateButtons(); | 944 | this.updateButtons(); |
| 780 | } | 945 | } |
| 781 | 946 | ||
| 947 | // Setup RenderCanvas2D as a child class of Render | ||
| 782 | RenderCanvas2D.prototype = Object.create(Render.prototype); | 948 | RenderCanvas2D.prototype = Object.create(Render.prototype); |
| 783 | RenderCanvas2D.prototype.constructor = RenderCanvas2D; | 949 | RenderCanvas2D.prototype.constructor = RenderCanvas2D; |
| 784 | 950 | ||
| 951 | // | ||
| 952 | // Performs the bulk of the work of resetting and rendering the maze. This is | ||
| 953 | // called whenever anything changes at all and the entire maze floor is redrawn. | ||
| 954 | // | ||
| 955 | // Since these are such simple graphics there's not much of an issue with this | ||
| 956 | // approach. It could be optomized and only the parts that have changed could | ||
| 957 | // bo modified, but it's probably not worth it in the long run. We're as | ||
| 958 | // likely to travel along any dimension as X or Y, and every dimension other | ||
| 959 | // than X and Y requrie a full redraw of the maze. | ||
| 960 | // | ||
| 785 | RenderCanvas2D.prototype.render = function render() | 961 | RenderCanvas2D.prototype.render = function render() |
| 786 | { | 962 | { |
| 787 | let iSize = this.iCellSize; | 963 | let iSize = this.iCellSize; |
| @@ -895,6 +1071,10 @@ RenderCanvas2D.prototype.render = function render() | |||
| 895 | } | 1071 | } |
| 896 | } | 1072 | } |
| 897 | 1073 | ||
| 1074 | // | ||
| 1075 | // Helper function that draws the icons for the travel icons for dimensions | ||
| 1076 | // after the first two. | ||
| 1077 | // | ||
| 898 | RenderCanvas2D.prototype.renderDirIcon = function renderDirIcon( | 1078 | RenderCanvas2D.prototype.renderDirIcon = function renderDirIcon( |
| 899 | x, y, iIcon, iDir ) | 1079 | x, y, iIcon, iDir ) |
| 900 | { | 1080 | { |
| @@ -952,6 +1132,10 @@ RenderCanvas2D.prototype.renderDirIcon = function renderDirIcon( | |||
| 952 | } | 1132 | } |
| 953 | } | 1133 | } |
| 954 | 1134 | ||
| 1135 | // | ||
| 1136 | // Slot that updates the enabled/disabled status of the UI buttons after the | ||
| 1137 | // player's position has changed. | ||
| 1138 | // | ||
| 955 | RenderCanvas2D.prototype.updateButtons = function updateButtons() | 1139 | RenderCanvas2D.prototype.updateButtons = function updateButtons() |
| 956 | { | 1140 | { |
| 957 | let c = this.rMap.get( this.rMap.pPlayer ); | 1141 | let c = this.rMap.get( this.rMap.pPlayer ); |
| @@ -961,6 +1145,10 @@ RenderCanvas2D.prototype.updateButtons = function updateButtons() | |||
| 961 | } | 1145 | } |
| 962 | } | 1146 | } |
| 963 | 1147 | ||
| 1148 | // | ||
| 1149 | // Slot that updates the textual readout label telling us what "floor" we're on | ||
| 1150 | // when the player's position has changed. | ||
| 1151 | // | ||
| 964 | RenderCanvas2D.prototype.updateReadout = function updateReadout() | 1152 | RenderCanvas2D.prototype.updateReadout = function updateReadout() |
| 965 | { | 1153 | { |
| 966 | if( this.rMap.getDims() <= 2 ) | 1154 | if( this.rMap.getDims() <= 2 ) |
| @@ -975,6 +1163,10 @@ RenderCanvas2D.prototype.updateReadout = function updateReadout() | |||
| 975 | this.readoutNode.textContent = text; | 1163 | this.readoutNode.textContent = text; |
| 976 | } | 1164 | } |
| 977 | 1165 | ||
| 1166 | // | ||
| 1167 | // Slot that performs the operations needed to display that a victory has | ||
| 1168 | // occured when the player has reached the exit. | ||
| 1169 | // | ||
| 978 | RenderCanvas2D.prototype.setVictory = function setVictory() | 1170 | RenderCanvas2D.prototype.setVictory = function setVictory() |
| 979 | { | 1171 | { |
| 980 | while( this.btnBox.hasChildNodes() ) | 1172 | while( this.btnBox.hasChildNodes() ) |
| @@ -989,15 +1181,6 @@ RenderCanvas2D.prototype.setVictory = function setVictory() | |||
| 989 | this.btnBox.appendChild( h1 ); | 1181 | this.btnBox.appendChild( h1 ); |
| 990 | } | 1182 | } |
| 991 | 1183 | ||
| 992 | RenderCanvas2D.prototype.setFloor = function setFloor( aFloor ) | ||
| 993 | { | ||
| 994 | for( let j = 0; j < aFloor.length; j++ ) | ||
| 995 | { | ||
| 996 | this.pExtPosition.set( j+2, aFloor[j] ); | ||
| 997 | } | ||
| 998 | this.render(); | ||
| 999 | } | ||
| 1000 | |||
| 1001 | // | 1184 | // |
| 1002 | // Initialize | 1185 | // Initialize |
| 1003 | // | 1186 | // |
| @@ -1038,7 +1221,11 @@ function lostInit( properties ) | |||
| 1038 | let eEditorBox = document.getElementById(properties['editor']); | 1221 | let eEditorBox = document.getElementById(properties['editor']); |
| 1039 | let eShareBox = document.getElementById(properties['share']); | 1222 | let eShareBox = document.getElementById(properties['share']); |
| 1040 | 1223 | ||
| 1041 | let rend = new RenderCanvas2D( m, properties['render']['params'] ); | 1224 | //let rend = new RenderCanvas2D( m, properties['render']['params'] ); |
| 1225 | let rend = Reflect.construct( | ||
| 1226 | properties['render']['name'], | ||
| 1227 | [m, properties['render']['params']] | ||
| 1228 | ); | ||
| 1042 | 1229 | ||
| 1043 | let formDiv = eEditorBox; | 1230 | let formDiv = eEditorBox; |
| 1044 | let form = document.createElement('form'); | 1231 | let form = document.createElement('form'); |
