summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Buland <Mike.Buland@mjfirm.com>2016-10-20 15:35:30 -0600
committerMike Buland <Mike.Buland@mjfirm.com>2016-10-20 15:35:30 -0600
commit9fb42f1e2e1361a07f0fca213dfb914d71763781 (patch)
treeb009c1ab1726f8be7e7ee87456099f68f564de48
parent2c857684d920f502d72a42f97f7b57e5e72ba2f8 (diff)
downloadlost-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.html7
-rw-r--r--js/lost.js549
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.
9function randInt( max )
10{
11 return Math.floor(Math.random() * max);
12}
13
14// Return the id for the opposite direction of the direction given.
15function oppositeDir( iDir )
16{
17 if( iDir%2 === 1 )
18 return iDir-1;
19 return iDir+1;
20}
21
22//
23// Class: Cell
24//
25function Cell()
26{
27 this.iDist = 0;
28 this.iPath = 0;
29 this.iWalls = 0;
30}
31
32//
33// Class: Position
34//
35function 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
69Position.prototype.getDims = function getDims()
70{
71 return this.iDims;
72}
73
74Position.prototype.get = function get( iDim )
75{
76 return this.aiValues[iDim];
77}
78
79Position.prototype.set = function set( iDim, iVal )
80{
81 this.aiValues[iDim] = iVal;
82}
83
84Position.prototype.add = function add( iDim, iDelta )
85{
86 this.aiValues[iDim] += iDelta;
87 return this.aiValues[iDim];
88}
89
90Position.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
97Position.prototype.copy = function translate()
98{
99 return new Position( this.iDims, ...this.aiValues.slice() );
100}
101
102//
103// Class: Map
104//
105function 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
125Map.prototype.getDims = function getDims()
126{
127 return this.Dimensions.getDims();
128}
129
130Map.prototype.getSize = function getSize( iDim )
131{
132 return this.Dimensions.get( iDim );
133}
134
135Map.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
155Map.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
172Map.prototype.get = function get( Position )
173{
174 return this.aCell[this.getIndex( Position )];
175}
176
177Map.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//
249function Vector( pPos, iDir )
250{
251 this.pPos = pPos;
252 this.iDir = iDir;
253}
254
255//
256// Class: Worm
257//
258function 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
291Worm.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//
369function Render( rMap, eParent )
370{
371 this.rMap = rMap;
372 this.eParent = eParent;
373}
374
375Render.prototype.render = function render()
376{
377}
378
379//
380// Class: RenderCanvas2D
381//
382function 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
431RenderCanvas2D.prototype = Object.create(Render.prototype);
432RenderCanvas2D.prototype.constructor = RenderCanvas2D;
433
434RenderCanvas2D.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
512RenderCanvas2D.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//
524let p = new Position( 3, 15, 15, 3 );
525let m = new Map( p );
526let exit1 = new Position( p.getDims() );
527let exit2 = new Position( p.getDims() );
528exit2.set( 1, p.get( 1 )-1 );
529let w = [
530 new Worm( 1, exit1, m ),
531 new Worm( 2, exit2, m )
532 ];
533
534do
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
546m.connect( 1, 2 );
547
548let rend = new RenderCanvas2D( m, document.body );
549//rend.render( document.body );