diff options
author | Mike Buland <Mike.Buland@mjfirm.com> | 2016-10-20 15:35:30 -0600 |
---|---|---|
committer | Mike Buland <Mike.Buland@mjfirm.com> | 2016-10-20 15:35:30 -0600 |
commit | 9fb42f1e2e1361a07f0fca213dfb914d71763781 (patch) | |
tree | b009c1ab1726f8be7e7ee87456099f68f564de48 | |
parent | 2c857684d920f502d72a42f97f7b57e5e72ba2f8 (diff) | |
download | lost-9fb42f1e2e1361a07f0fca213dfb914d71763781.tar.gz lost-9fb42f1e2e1361a07f0fca213dfb914d71763781.tar.bz2 lost-9fb42f1e2e1361a07f0fca213dfb914d71763781.tar.xz lost-9fb42f1e2e1361a07f0fca213dfb914d71763781.zip |
Hey! it renders and has multiple levels.
It generates buttons so you can see each floor for now.
-rw-r--r-- | js/lost.html | 7 | ||||
-rw-r--r-- | js/lost.js | 549 |
2 files changed, 556 insertions, 0 deletions
diff --git a/js/lost.html b/js/lost.html new file mode 100644 index 0000000..38f246e --- /dev/null +++ b/js/lost.html | |||
@@ -0,0 +1,7 @@ | |||
1 | <html> | ||
2 | <head> | ||
3 | </head> | ||
4 | <body> | ||
5 | <script src="lost.js"></script> | ||
6 | </body> | ||
7 | </html> | ||
diff --git a/js/lost.js b/js/lost.js new file mode 100644 index 0000000..b2b908c --- /dev/null +++ b/js/lost.js | |||
@@ -0,0 +1,549 @@ | |||
1 | "use strict"; | ||
2 | // It's strict! | ||
3 | |||
4 | // | ||
5 | // Helper functions | ||
6 | // | ||
7 | |||
8 | // Just return a random integer between 0 and max, exclusive on the upper bound. | ||
9 | function randInt( max ) | ||
10 | { | ||
11 | return Math.floor(Math.random() * max); | ||
12 | } | ||
13 | |||
14 | // Return the id for the opposite direction of the direction given. | ||
15 | function oppositeDir( iDir ) | ||
16 | { | ||
17 | if( iDir%2 === 1 ) | ||
18 | return iDir-1; | ||
19 | return iDir+1; | ||
20 | } | ||
21 | |||
22 | // | ||
23 | // Class: Cell | ||
24 | // | ||
25 | function Cell() | ||
26 | { | ||
27 | this.iDist = 0; | ||
28 | this.iPath = 0; | ||
29 | this.iWalls = 0; | ||
30 | } | ||
31 | |||
32 | // | ||
33 | // Class: Position | ||
34 | // | ||
35 | function Position( iDims, ...Vals ) | ||
36 | { | ||
37 | // Store dimension count | ||
38 | this.iDims = iDims; | ||
39 | |||
40 | // Check to see if Vals is a non-empty array | ||
41 | if( Array.isArray(Vals) && Vals.length > 0 ) | ||
42 | { | ||
43 | // Make sure Vals has the right number of elements | ||
44 | if( Vals.length != iDims ) | ||
45 | { | ||
46 | throw new Error( | ||
47 | 'Position must be initialized with no dimensional data, '+ | ||
48 | 'or the correct number of elements.'); | ||
49 | } | ||
50 | |||
51 | // If it does have the correct number of elements, just | ||
52 | // use it instead of creating a new array | ||
53 | this.aiValues = Vals; | ||
54 | } | ||
55 | else | ||
56 | { | ||
57 | // We don't have values from the constructor, let's just | ||
58 | // create a new blank one... | ||
59 | this.aiValues = new Array( this.iDims ); | ||
60 | |||
61 | // ...and set the position to zero in all dimensions. | ||
62 | for( let j = 0; j < this.iDims; j++ ) | ||
63 | { | ||
64 | this.aiValues[j] = 0; | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | |||
69 | Position.prototype.getDims = function getDims() | ||
70 | { | ||
71 | return this.iDims; | ||
72 | } | ||
73 | |||
74 | Position.prototype.get = function get( iDim ) | ||
75 | { | ||
76 | return this.aiValues[iDim]; | ||
77 | } | ||
78 | |||
79 | Position.prototype.set = function set( iDim, iVal ) | ||
80 | { | ||
81 | this.aiValues[iDim] = iVal; | ||
82 | } | ||
83 | |||
84 | Position.prototype.add = function add( iDim, iDelta ) | ||
85 | { | ||
86 | this.aiValues[iDim] += iDelta; | ||
87 | return this.aiValues[iDim]; | ||
88 | } | ||
89 | |||
90 | Position.prototype.translate = function translate( iDim, iDelta ) | ||
91 | { | ||
92 | let tmp = new Position( this.iDims, ...this.aiValues.slice() ); | ||
93 | tmp.add( iDim, iDelta ); | ||
94 | return tmp; | ||
95 | } | ||
96 | |||
97 | Position.prototype.copy = function translate() | ||
98 | { | ||
99 | return new Position( this.iDims, ...this.aiValues.slice() ); | ||
100 | } | ||
101 | |||
102 | // | ||
103 | // Class: Map | ||
104 | // | ||
105 | function Map( Dimensions ) | ||
106 | { | ||
107 | // Store dimensional data | ||
108 | this.Dimensions = Dimensions; | ||
109 | |||
110 | // Compute the total number of cells | ||
111 | let iTotalSize = 1; | ||
112 | for( let j = 0; j < Dimensions.getDims(); j++ ) | ||
113 | { | ||
114 | iTotalSize *= Dimensions.get( j ); | ||
115 | } | ||
116 | |||
117 | // Allocate cell array, and initialize cells | ||
118 | this.aCell = new Array( iTotalSize ); | ||
119 | for( let j = 0; j < iTotalSize; j++ ) | ||
120 | { | ||
121 | this.aCell[j] = new Cell(); | ||
122 | } | ||
123 | } | ||
124 | |||
125 | Map.prototype.getDims = function getDims() | ||
126 | { | ||
127 | return this.Dimensions.getDims(); | ||
128 | } | ||
129 | |||
130 | Map.prototype.getSize = function getSize( iDim ) | ||
131 | { | ||
132 | return this.Dimensions.get( iDim ); | ||
133 | } | ||
134 | |||
135 | Map.prototype.isInside = function isInside( Position ) | ||
136 | { | ||
137 | if( Position.getDims() != this.Dimensions.getDims() ) | ||
138 | { | ||
139 | throw new Error( | ||
140 | 'Number of dimensions in map and position do not match.' | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | for( let j = 0; j < this.getDims(); j++ ) | ||
145 | { | ||
146 | if( Position.get( j ) < 0 ) | ||
147 | return false; | ||
148 | if( Position.get( j ) >= this.Dimensions.get( j ) ) | ||
149 | return false; | ||
150 | } | ||
151 | |||
152 | return true; | ||
153 | } | ||
154 | |||
155 | Map.prototype.getIndex = function getIndex( Position ) | ||
156 | { | ||
157 | if( !this.isInside( Position ) ) | ||
158 | { | ||
159 | throw new Error('Position is outside of map.'); | ||
160 | } | ||
161 | |||
162 | let iIdx = 0; | ||
163 | let iScale = 1; | ||
164 | for( let j = 0; j < this.getDims(); j++ ) | ||
165 | { | ||
166 | iIdx += Position.get( j ) * iScale; | ||
167 | iScale *= this.Dimensions.get( j ); | ||
168 | } | ||
169 | return iIdx; | ||
170 | } | ||
171 | |||
172 | Map.prototype.get = function get( Position ) | ||
173 | { | ||
174 | return this.aCell[this.getIndex( Position )]; | ||
175 | } | ||
176 | |||
177 | Map.prototype.connect = function connect( iWormId1, iWormId2 ) | ||
178 | { | ||
179 | let p = new Position( this.getDims() ); | ||
180 | let pMax1 = null; | ||
181 | let pMax2 = null; | ||
182 | let iDistMax = 0; | ||
183 | let iDirMax = 0; | ||
184 | |||
185 | let iDim = 0; | ||
186 | for(;;) | ||
187 | { | ||
188 | let c = this.get( p ); | ||
189 | if( c.iPath === iWormId1 || c.iPath === iWormId2 ) | ||
190 | { | ||
191 | // This cell is one of the two paths we want to connect, let's | ||
192 | // see if there's a cell from the other path nearby. | ||
193 | for( iDim = 0; iDim < this.getDims(); iDim++ ) | ||
194 | { | ||
195 | // Look 'down' in the current dimension | ||
196 | let t = p.translate( iDim, -1 ); | ||
197 | for( let iDir = 0; iDir < 2; iDir++ ) | ||
198 | { | ||
199 | // Is the current position inside the maze? | ||
200 | if( t.get( iDim ) >= 0 && | ||
201 | t.get( iDim ) < this.getSize( iDim ) ) | ||
202 | { | ||
203 | // Get cell here. | ||
204 | let c2 = this.get( t ); | ||
205 | if( c.iPath !== c2.iPath && | ||
206 | (c2.iPath === iWormId1 || c2.iPath === iWormId2 ) ) | ||
207 | { | ||
208 | let iDist = c.iDist + c2.iDist; | ||
209 | if( iDist > iDistMax ) | ||
210 | { | ||
211 | iDistMax = iDist; | ||
212 | pMax1 = p.copy(); | ||
213 | pMax2 = t.copy(); | ||
214 | iDirMax = iDim*2+iDir; | ||
215 | } | ||
216 | } | ||
217 | } | ||
218 | |||
219 | // Look the other direction | ||
220 | t.add( iDim, 2 ); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | |||
225 | // This is the rediculous engine that lets us iterate through | ||
226 | // the entire maze, one cell at a time. This basically increments our | ||
227 | // position by one, but wraps at the edges of the maze. | ||
228 | for( iDim = 0; iDim < this.getDims(); iDim++ ) | ||
229 | { | ||
230 | let iNewVal = p.add( iDim, 1 ); | ||
231 | if( iNewVal < this.getSize( iDim ) ) | ||
232 | break; | ||
233 | p.set( iDim, 0 ); | ||
234 | } | ||
235 | |||
236 | // If we ran out of dimensions then it means that we hit the last | ||
237 | // cell in the grid. | ||
238 | if( iDim == this.getDims() ) | ||
239 | break; | ||
240 | } | ||
241 | |||
242 | this.get( pMax1 ).iWalls |= (1<<iDirMax); | ||
243 | this.get( pMax2 ).iWalls |= (1<<oppositeDir(iDirMax)); | ||
244 | } | ||
245 | |||
246 | // | ||
247 | // Class: Vector | ||
248 | // | ||
249 | function Vector( pPos, iDir ) | ||
250 | { | ||
251 | this.pPos = pPos; | ||
252 | this.iDir = iDir; | ||
253 | } | ||
254 | |||
255 | // | ||
256 | // Class: Worm | ||
257 | // | ||
258 | function Worm( iId, pStart, rMap ) | ||
259 | { | ||
260 | // Initialize basic state, we start with distance set to 1 | ||
261 | this.iId = iId; | ||
262 | this.pPosition = pStart; | ||
263 | this.rMap = rMap; | ||
264 | this.iDist = 1; | ||
265 | |||
266 | // Setup walls here to create an opening. We're only going to do | ||
267 | // this on the first two dimensions. This assumes that we have at least | ||
268 | // two dimensions, which I feel like is a safe assumption. 1d mazes are... | ||
269 | // pretty easy. | ||
270 | let iDirs = []; | ||
271 | if( this.pPosition.get( 0 ) === 0 ) | ||
272 | iDirs.push( 1 ); | ||
273 | else if( this.pPosition.get( 0 ) === this.rMap.getSize( 0 )-1 ) | ||
274 | iDirs.push( 2 ); | ||
275 | |||
276 | if( this.pPosition.get( 1 ) === 0 ) | ||
277 | iDirs.push( 4 ); | ||
278 | else if( this.pPosition.get( 1 ) === this.rMap.getSize( 1 )-1 ) | ||
279 | iDirs.push( 8 ); | ||
280 | |||
281 | // Now that we know if we're near a wall in the first two demensions | ||
282 | // do something with that | ||
283 | if( iDirs.length > 0 ) | ||
284 | { | ||
285 | // We are near a wall, pick a random wall to open a hole in | ||
286 | this.rMap.get(this.pPosition).iWalls |= iDirs[randInt(iDirs.length)]; | ||
287 | this.rMap.get(this.pPosition).iPath = this.iId; | ||
288 | } | ||
289 | } | ||
290 | |||
291 | Worm.prototype.timestep = function timestep() | ||
292 | { | ||
293 | // Handy to reference how many dimensions we have | ||
294 | let iDims = this.rMap.getDims(); | ||
295 | |||
296 | // Possible directions | ||
297 | let pDirs = []; | ||
298 | |||
299 | for(;;) | ||
300 | { | ||
301 | let pBack = null; | ||
302 | for( let j = 0; j < iDims; j++ ) | ||
303 | { | ||
304 | let iSize = this.rMap.getSize( j ); | ||
305 | let pPos = this.pPosition.translate( j, -1 ); | ||
306 | if( pPos.get( j ) >= 0 ) | ||
307 | { | ||
308 | let xCell = this.rMap.get( pPos ); | ||
309 | if( xCell.iPath === 0 ) | ||
310 | { | ||
311 | pDirs.push( new Vector( pPos, j*2 ) ); | ||
312 | } | ||
313 | else if( xCell.iPath === this.iId && | ||
314 | xCell.iDist === this.iDist-1 ) | ||
315 | { | ||
316 | pBack = pPos; | ||
317 | } | ||
318 | } | ||
319 | |||
320 | pPos = this.pPosition.translate( j, 1 ); | ||
321 | if( pPos.get( j ) < iSize ) | ||
322 | { | ||
323 | let xCell = this.rMap.get( pPos ); | ||
324 | if( xCell.iPath === 0 ) | ||
325 | { | ||
326 | pDirs.push( new Vector( pPos, j*2+1 ) ); | ||
327 | } | ||
328 | else if( xCell.iPath === this.iId && | ||
329 | xCell.iDist === this.iDist-1 ) | ||
330 | { | ||
331 | pBack = pPos; | ||
332 | } | ||
333 | } | ||
334 | } | ||
335 | |||
336 | if( pDirs.length > 0 ) | ||
337 | { | ||
338 | break; | ||
339 | } | ||
340 | else | ||
341 | { | ||
342 | if( pBack !== null ) | ||
343 | { | ||
344 | this.pPosition = pBack; | ||
345 | this.iDist--; | ||
346 | } | ||
347 | else | ||
348 | { | ||
349 | return false; | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | |||
354 | let iSel = randInt( pDirs.length ); | ||
355 | let cCur = this.rMap.get( this.pPosition ); | ||
356 | cCur.iWalls |= (1<<pDirs[iSel].iDir); | ||
357 | let cNext = this.rMap.get( pDirs[iSel].pPos ); | ||
358 | cNext.iWalls |= (1<<oppositeDir( pDirs[iSel].iDir )); | ||
359 | cNext.iDist = ++this.iDist; | ||
360 | cNext.iPath = this.iId; | ||
361 | this.pPosition = pDirs[iSel].pPos; | ||
362 | |||
363 | return true; | ||
364 | } | ||
365 | |||
366 | // | ||
367 | // Class: Render | ||
368 | // | ||
369 | function Render( rMap, eParent ) | ||
370 | { | ||
371 | this.rMap = rMap; | ||
372 | this.eParent = eParent; | ||
373 | } | ||
374 | |||
375 | Render.prototype.render = function render() | ||
376 | { | ||
377 | } | ||
378 | |||
379 | // | ||
380 | // Class: RenderCanvas2D | ||
381 | // | ||
382 | function RenderCanvas2D( rMap, eParent ) | ||
383 | { | ||
384 | Render.call( this, rMap, eParent ); | ||
385 | |||
386 | this.eCanvas = null; | ||
387 | this.ctx = null; | ||
388 | |||
389 | this.pExtPosition = new Position( rMap.getDims() ); | ||
390 | |||
391 | this.iIconSize = 11; | ||
392 | this.iBorder = 3; | ||
393 | this.iCellSize = | ||
394 | this.iBorder + | ||
395 | (this.rMap.getDims()-2)*2*(this.iIconSize+this.iBorder); | ||
396 | |||
397 | this.eCanvas = document.createElement('canvas'); | ||
398 | this.eCanvas.width = this.iCellSize*this.rMap.getSize( 0 ); | ||
399 | this.eCanvas.height = this.iCellSize*this.rMap.getSize( 1 ); | ||
400 | eParent.appendChild( this.eCanvas ); | ||
401 | this.ctx = this.eCanvas.getContext("2d"); | ||
402 | this.ctx.lineWidth = 1.0; | ||
403 | |||
404 | this.render(); | ||
405 | |||
406 | let btnBox = document.createElement('div'); | ||
407 | eParent.appendChild( document.createElement('br') ); | ||
408 | eParent.appendChild( document.createElement('br') ); | ||
409 | eParent.appendChild( btnBox ); | ||
410 | |||
411 | for( let j = 2; j < this.rMap.getDims(); j++ ) | ||
412 | { | ||
413 | for( let k = 0; k < this.rMap.getSize( j ); k++ ) | ||
414 | { | ||
415 | let btn = document.createElement('button'); | ||
416 | btn.addEventListener( | ||
417 | 'click', | ||
418 | RenderCanvas2D.prototype.setFloor.bind( | ||
419 | this, | ||
420 | [k] | ||
421 | ) | ||
422 | ); | ||
423 | btn.appendChild( | ||
424 | document.createTextNode('Floor ' + (k+1) ) | ||
425 | ); | ||
426 | btnBox.appendChild( btn ); | ||
427 | } | ||
428 | } | ||
429 | } | ||
430 | |||
431 | RenderCanvas2D.prototype = Object.create(Render.prototype); | ||
432 | RenderCanvas2D.prototype.constructor = RenderCanvas2D; | ||
433 | |||
434 | RenderCanvas2D.prototype.render = function render() | ||
435 | { | ||
436 | let iSize = this.iCellSize; | ||
437 | this.ctx.clearRect( 0, 0, this.eCanvas.width, this.eCanvas.height ); | ||
438 | this.ctx.beginPath(); | ||
439 | |||
440 | let p = this.pExtPosition.copy(); | ||
441 | { | ||
442 | for( let x = 0; x < this.rMap.getSize( 0 ); x++ ) | ||
443 | { | ||
444 | for( let y = 0; y < this.rMap.getSize( 1 ); y++ ) | ||
445 | { | ||
446 | p.set( 0, x ); | ||
447 | p.set( 1, y ); | ||
448 | let c = this.rMap.get( p ); | ||
449 | if( (c.iWalls&1) === 0 && x === 0) | ||
450 | { | ||
451 | this.ctx.moveTo( x*iSize, y*iSize ); | ||
452 | this.ctx.lineTo( x*iSize, (y+1)*iSize ); | ||
453 | } | ||
454 | if( (c.iWalls&2) === 0 ) | ||
455 | { | ||
456 | this.ctx.moveTo( (x+1)*iSize, y*iSize ); | ||
457 | this.ctx.lineTo( (x+1)*iSize, (y+1)*iSize ); | ||
458 | } | ||
459 | if( (c.iWalls&4) === 0 && y === 0) | ||
460 | { | ||
461 | this.ctx.moveTo( x*iSize, y*iSize ); | ||
462 | this.ctx.lineTo( (x+1)*iSize, y*iSize ); | ||
463 | } | ||
464 | if( (c.iWalls&8) === 0 ) | ||
465 | { | ||
466 | this.ctx.moveTo( x*iSize, (y+1)*iSize ); | ||
467 | this.ctx.lineTo( (x+1)*iSize, (y+1)*iSize ); | ||
468 | } | ||
469 | |||
470 | if( (c.iWalls&16) !== 0 ) | ||
471 | { | ||
472 | // Up | ||
473 | let bx = x*iSize+this.iBorder; | ||
474 | let by = y*iSize+this.iBorder; | ||
475 | this.ctx.moveTo( | ||
476 | bx, | ||
477 | by+this.iIconSize | ||
478 | ); | ||
479 | this.ctx.lineTo( | ||
480 | bx+this.iIconSize/2, | ||
481 | by | ||
482 | ); | ||
483 | this.ctx.lineTo( | ||
484 | bx+this.iIconSize, | ||
485 | by+this.iIconSize | ||
486 | ); | ||
487 | } | ||
488 | if( (c.iWalls&32) !== 0 ) | ||
489 | { | ||
490 | // Down | ||
491 | let bx = x*iSize+this.iBorder*2+this.iIconSize; | ||
492 | let by = y*iSize+this.iBorder; | ||
493 | this.ctx.moveTo( | ||
494 | bx, | ||
495 | by | ||
496 | ); | ||
497 | this.ctx.lineTo( | ||
498 | bx+this.iIconSize/2, | ||
499 | by+this.iIconSize | ||
500 | ); | ||
501 | this.ctx.lineTo( | ||
502 | bx+this.iIconSize, | ||
503 | by | ||
504 | ); | ||
505 | } | ||
506 | } | ||
507 | } | ||
508 | } | ||
509 | this.ctx.stroke(); | ||
510 | } | ||
511 | |||
512 | RenderCanvas2D.prototype.setFloor = function setFloor( aFloor ) | ||
513 | { | ||
514 | for( let j = 0; j < aFloor.length; j++ ) | ||
515 | { | ||
516 | this.pExtPosition.set( j+2, aFloor[j] ); | ||
517 | } | ||
518 | this.render(); | ||
519 | } | ||
520 | |||
521 | // | ||
522 | // Initialize | ||
523 | // | ||
524 | let p = new Position( 3, 15, 15, 3 ); | ||
525 | let m = new Map( p ); | ||
526 | let exit1 = new Position( p.getDims() ); | ||
527 | let exit2 = new Position( p.getDims() ); | ||
528 | exit2.set( 1, p.get( 1 )-1 ); | ||
529 | let w = [ | ||
530 | new Worm( 1, exit1, m ), | ||
531 | new Worm( 2, exit2, m ) | ||
532 | ]; | ||
533 | |||
534 | do | ||
535 | { | ||
536 | for( let j = 0; j < w.length; j++ ) | ||
537 | { | ||
538 | if( !w[j].timestep() ) | ||
539 | { | ||
540 | w.splice( j, 1 ); | ||
541 | j--; | ||
542 | } | ||
543 | } | ||
544 | } while( w.length > 0 ); | ||
545 | |||
546 | m.connect( 1, 2 ); | ||
547 | |||
548 | let rend = new RenderCanvas2D( m, document.body ); | ||
549 | //rend.render( document.body ); | ||