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 | |
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.
-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'); |