I havet this codepen: https://codepen.io/sp2012/pen/VwpyWdp . Unfortunately, this code is too advanced for me. The game has three maps. I want to keep only the first map and when the game is finished, if you click on the map the game restarts.
The code follows:
This is the HTML:
JavaScript
x
23
23
1
<div id="game-container-1" class="game-container">
2
3
<div id="map-and-controls">
4
<div id="game-map-1" class="game-map">
5
<div id="tiles" class="layer"></div>
6
<div id="sprites" class="layer"></div>
7
<div id="success-msg">Goal reached! Tap the maze to change levels.</div>
8
</div>
9
10
<!-- controls-->
11
<div id="controls">
12
<button id="up"></button>
13
<div id="horiz">
14
<button id="left"></button>
15
<button id="right"></button>
16
</div>
17
<button id="down"></button>
18
</div>
19
</div>
20
<p id="text-1" class="text">Use cursor keys or buttons to move the marble.</p>
21
22
</div>
23
This is the CSS:
JavaScript
1
236
236
1
/*
2
* General Styling
3
*/
4
body {
5
font-family: Calibri;
6
transition: 0.2s ease;
7
text-align: center;
8
}
9
body.success {
10
background-color: #b7f0b7;
11
transition: 0.2s ease;
12
}
13
/* center everything in game container */
14
.game-container {
15
margin: 0px auto;
16
}
17
/*
18
* Map screen
19
*/
20
21
.game-map {
22
position: relative;
23
}
24
/*
25
* Output text styles
26
*/
27
28
p {
29
margin: 10px 0px;
30
padding: 0px;
31
32
}
33
34
35
/*
36
* Map on left, controls on right
37
* Adapted for the mobile Medium app
38
*/
39
#map-and-controls {
40
display: flex;
41
justify-content: center;
42
}
43
/*
44
* Controls
45
*/
46
47
#controls {
48
margin-left: 10px;
49
text-align: center;
50
}
51
/*
52
* Container for right and left buttons
53
*/
54
#controls #horiz {
55
display: flex;
56
align-items: center;
57
justify-content: center;
58
}
59
/*
60
* General button styles
61
*/
62
#controls button {
63
padding: 10px 10px;
64
margin-top: 10px;
65
background-color: #DDD;
66
border: 1px solid #000;
67
width: 38px;
68
height: 38px;
69
border-radius: 3px;
70
cursor: pointer;
71
position: relative;
72
}
73
/*
74
* Spacing between horiz buttons
75
*/
76
button#right {
77
margin-left: 5px;
78
}
79
button#left {
80
margin-right: 5px;
81
}
82
83
/*
84
* General button arrow styles
85
*/
86
#controls button::before {
87
content:'';
88
width: 0px;
89
position: absolute;
90
}
91
/*
92
* Specific Arrow Styles
93
*/
94
button#left::before {
95
border-top: 10px solid transparent;
96
border-right: 15px solid #000;
97
border-bottom: 10px solid transparent;
98
left: 10px;
99
top: 9px;
100
}
101
button#right::before {
102
border-top: 10px solid transparent;
103
border-left: 15px solid #000;
104
border-bottom: 10px solid transparent;
105
left: 12px;
106
top: 9px;
107
}
108
button#up::before {
109
border-right: 10px solid transparent;
110
border-left: 10px solid transparent;
111
border-bottom: 15px solid #000;
112
left: 9px;
113
top: 9px;
114
}
115
button#down::before {
116
border-right: 10px solid transparent;
117
border-left: 10px solid transparent;
118
border-top: 15px solid #000;
119
left: 9px;
120
top: 12px;
121
}
122
#success-msg {
123
opacity: 0;
124
transition: opacity 0.2s ease;
125
position: absolute;
126
padding: 5px 5px;
127
background-color: rgba(0,0,0,0.5);
128
color: white;
129
width: calc(100% - 8px);
130
}
131
body.success #success-msg {
132
opacity: 1;
133
transition: opacity 0.2 ease;
134
}
135
136
137
138
/*
139
* Layers and tiles are positioned absolutely
140
* within coordinate system of .game-map
141
*/
142
div.layer,
143
div.layer div {
144
position: absolute;
145
}
146
/* border for floors and wall */
147
#tiles div {
148
border: 1px solid grey;
149
}
150
151
/*
152
* Default wall and floor styles
153
*/
154
155
.default .floor {
156
background-color: lightgrey;
157
}
158
159
.default .wall {
160
background-color: skyblue;
161
}
162
/*
163
* grassland theme
164
*/
165
.grassland .floor {
166
background-color: #7bb76d;
167
}
168
.grassland .wall {
169
background-color: #806d51;
170
}
171
.grassland #player {
172
background-color: #b2ccec;
173
}
174
175
/*
176
* dungeon theme
177
*/
178
.dungeon .floor {
179
background-color: darkgrey;
180
}
181
.dungeon .wall {
182
background-color: #9c649c;
183
}
184
.dungeon #player {
185
background-color: #ab1431;
186
}
187
/*
188
* player and goal are slightly smaller than tiles
189
*/
190
.player,
191
.goal {
192
transform-origin: center;
193
transform:scale(0.85);
194
}
195
/*
196
* Goal colors
197
*/
198
.goal {
199
background-color: #FFD700;
200
border: 1px solid #98720b;
201
}
202
/*
203
* Player default colors
204
*/
205
.player {
206
background-color: #90ee90;
207
border: 1px solid #008000;
208
transition: left 0.2s ease, top 0.2s ease;
209
}
210
/*
211
* Player wobbles when colliding with wall or border
212
*/
213
.player.collide {
214
animation: wobble 0.5s;
215
animation-iteration-count: infinite;
216
transition: background-color 0.2s;
217
218
}
219
220
/*
221
* Wobble animation
222
*/
223
@keyframes wobble {
224
0% { transform: scale(0.85) translate(1px, 1px); }
225
10% { transform: scale(0.85) translate(-1px, -2px); }
226
20% { transform: scale(0.85) translate(-3px, 0px); }
227
30% { transform: scale(0.85) translate(3px, 2px); }
228
40% { transform: scale(0.85) translate(1px, -1px);}
229
50% { transform: scale(0.85) translate(-1px, 2px); }
230
60% { transform: scale(0.85) translate(-3px, 1px); }
231
70% { transform: scale(0.85) translate(3px, 1px); }
232
80% { transform: scale(0.85) translate(-1px, -1px); }
233
90% { transform: scale(0.85) translate(1px, 2px); }
234
100% { transform: scale(0.85) translate(1px, -2px);; }
235
}
236
Here is the JavaScript:
JavaScript
1
523
523
1
let app = {};
2
3
4
(function(context) {
5
6
/*
7
* Build an array of levels.
8
* This will scale better if it is stored in a separate JSON File.
9
*/
10
let levels = [];
11
levels[0] = {
12
map:[
13
[1,1,0,0,1],
14
[1,0,0,0,0],
15
[0,0,1,1,0],
16
[0,0,0,1,0],
17
[0,1,0,1,0]
18
],
19
20
player:{
21
x:0,
22
y:4
23
},
24
goal:{
25
x:4,
26
y:1
27
},
28
theme:'default',
29
};
30
// second level
31
levels[1] = {
32
map:[
33
[1,0,1,1,1,1],
34
[0,0,0,0,0,0],
35
[0,1,1,1,0,0],
36
[0,0,0,1,1,0],
37
[0,1,0,1,0,0]
38
],
39
theme:'grassland',
40
player:{
41
x:2,
42
y:4
43
},
44
goal:{
45
x:4,
46
y:4
47
}
48
};
49
// third level
50
levels[2] = {
51
map:[
52
[1,0,1,0,0,1,0],
53
[0,0,0,0,0,1,0],
54
[1,0,1,1,0,0,0],
55
[1,0,0,1,0,1,0],
56
[1,1,0,0,1,0,0]
57
],
58
theme:'dungeon',
59
player:{
60
x:2,
61
y:4
62
},
63
goal:{
64
x:6,
65
y:4
66
}
67
};
68
69
70
/*
71
* The game object constructor.
72
* @param {String} id - the id of the game container DOM element.
73
* @param {Object} level - the starting level of the game.
74
*/
75
function Game(id,level) {
76
77
this.el = document.getElementById(id);
78
79
// level addition
80
this.level_idx = 0;
81
82
// establish the basic properties common to all this objects.
83
this.tileTypes = ['floor','wall'];
84
this.tileDim = 32;
85
// inherit the level's properties: map, player start, goal start.
86
this.map = level.map;
87
88
// level switch
89
this.theme = level.theme;
90
91
// make a copy of the level's player.
92
this.player = {level.player};
93
94
// create a property for the DOM element, to be set later.
95
this.player.el = null;
96
97
// make a copy of the goal.
98
this.goal = {level.goal};
99
}
100
101
/*
102
* Create a tile or sprite <div> element.
103
* @param {Number} x - the horizontal coordinate the 2D array.
104
* @param {Number} y - the vertical coordinate in the 2D array.
105
*/
106
Game.prototype.createEl = function(x,y,type) {
107
// create one tile.
108
let el = document.createElement('div');
109
110
// two class names: one for tile, one or the tile type.
111
el.className = type;
112
113
// set width and height of tile based on the passed-in dimensions.
114
el.style.width = el.style.height = this.tileDim + 'px';
115
116
// set left positions based on x coordinate.
117
el.style.left = x*this.tileDim + 'px';
118
119
// set top position based on y coordinate.
120
el.style.top = y*this.tileDim + 'px';
121
122
return el;
123
}
124
125
/*
126
* Applies the level theme as a class to the game element.
127
* Populates the map by adding tiles and sprites to their respective layers.
128
*/
129
Game.prototype.populateMap = function() {
130
131
// add theme call
132
this.el.className = 'game-container ' + this.theme;
133
134
// make a reference to the tiles layer in the DOM.
135
let tiles = this.el.querySelector('#tiles');
136
137
// set up our loop to populate the grid.
138
for (var y = 0; y < this.map.length; ++y) {
139
for (var x = 0; x < this.map[y].length; ++x) {
140
141
let tileCode = this.map[y][x];
142
143
// determine tile type using code
144
// index into the tileTypes array using the code.
145
let tileType = this.tileTypes[tileCode];
146
147
// call the helper function
148
let tile = this.createEl(x,y,tileType);
149
150
// add to layer
151
tiles.appendChild(tile);
152
}
153
}
154
}
155
156
/*
157
* Place the player or goal sprite.
158
* @param {String} type - either 'player' or 'goal', used by createEl and becomes DOM ID
159
*/
160
Game.prototype.placeSprite = function(type) {
161
162
// syntactic sugar
163
let x = this[type].x
164
165
let y = this[type].y;
166
167
// reuse the createTile function
168
let sprite = this.createEl(x,y,type);
169
170
sprite.id = type;
171
172
// set the border radius of the sprite.
173
sprite.style.borderRadius = this.tileDim + 'px';
174
175
// get half the difference between tile and sprite.
176
177
// grab the layer
178
let layer = this.el.querySelector('#sprites');
179
180
layer.appendChild(sprite);
181
182
return sprite;
183
}
184
185
/*
186
* Triggers a collide animation on the player sprite.
187
*/
188
Game.prototype.collide = function() {
189
this.player.el.className += ' collide';
190
191
let obj = this;
192
193
window.setTimeout(function() {
194
obj.player.el.className = 'player';
195
},200);
196
197
return 0;
198
199
};
200
/*
201
* Moves the player sprite left.
202
*/
203
Game.prototype.moveLeft = function() {
204
// if at the boundary, return
205
if (this.player.x == 0) {
206
this.collide();
207
return;
208
}
209
// itentify next tile
210
let nextTile = this.map[this.player.y][this.player.x-1];
211
212
// if next tile is a wall, add collide effect and return
213
if (nextTile ==1) {
214
this.collide();
215
return;
216
}
217
// change coordinates of player object
218
this.player.x -=1;
219
// update location of DOM element
220
this.updateHoriz();
221
};
222
/*
223
* Moves the player sprite up.
224
*/
225
Game.prototype.moveUp = function() {
226
if (this.player.y == 0) {
227
// at end: these could be combined
228
this.collide();
229
return;
230
}
231
232
let nextTile = this.map[this.player.y-1][this.player.x];
233
if (nextTile ==1) {
234
this.collide();
235
return;
236
}
237
this.player.y -=1;
238
this.updateVert();
239
240
};
241
/*
242
* Moves the player sprite right.
243
*/
244
Game.prototype.moveRight = function() {
245
if (this.player.x == this.map[this.player.y].length-1) {
246
this.collide();
247
return;
248
}
249
nextTile = this.map[this.player.y][this.player.x+1];
250
251
if (nextTile ==1) {
252
this.collide()
253
return;
254
}
255
this.player.x += 1;
256
257
this.updateHoriz();
258
};
259
/*
260
* Moves player sprite down.
261
*/
262
Game.prototype.moveDown = function() {
263
if (this.player.y == this.map.length-1) {
264
this.collide();
265
return;
266
}
267
// find the next tile in the 2D array.
268
269
let nextTile = this.map[this.player.y+1][this.player.x];
270
if (nextTile ==1) {
271
this.collide()
272
return;
273
}
274
this.player.y += 1;
275
this.updateVert();
276
277
};
278
/*
279
* Updates vertical position of player sprite based on object's y coordinates.
280
*/
281
Game.prototype.updateVert = function() {
282
this.player.el.style.top = this.player.y * this.tileDim+ 'px';
283
};
284
/*
285
* Updates horizontal position of player sprite based on object's x coordinates.
286
*/
287
Game.prototype.updateHoriz = function() {
288
this.player.el.style.left = this.player.x * this.tileDim + 'px';
289
};
290
/*
291
* Moves player based on keyboard cursor presses.
292
*/
293
Game.prototype.movePlayer = function(event) {
294
event.preventDefault();
295
296
if (event.keyCode < 37 || event.keyCode > 40) {
297
return;
298
}
299
300
switch (event.keyCode) {
301
case 37:
302
this.moveLeft();
303
break;
304
305
case 38:
306
this.moveUp();
307
break;
308
309
case 39:
310
this.moveRight();
311
break;
312
313
case 40:
314
this.moveDown();
315
break;
316
}
317
}
318
/*
319
* Check on whether goal has been reached.
320
*/
321
Game.prototype.checkGoal = function() {
322
let body = document.querySelector('body');
323
324
if (this.player.y == this.goal.y &&
325
this.player.x == this.goal.x) {
326
327
body.className = 'success';
328
}
329
else {
330
body.className = '';
331
}
332
}
333
/*
334
* Changes the level of the game object.
335
*/
336
Game.prototype.changeLevel = function() {
337
338
// update the level index.
339
this.level_idx ++;
340
341
// if higher than max index, set back to zero.
342
if (this.level_idx > levels.length -1) {
343
this.level_idx = 0;
344
}
345
346
// get the level at this index.
347
let level = levels[this.level_idx];
348
349
// sync the map with the level map.
350
this.map = level.map;
351
// sync the theme with the level theme.
352
this.theme = level.theme;
353
354
// make a copy of the level's player object, since x and y change during the game.
355
this.player = {level.player};
356
357
// make a copy of the level's goal object, since x and y change between levels.
358
this.goal = {level.goal};
359
}
360
361
/*
362
* If goal has been reached,
363
*/
364
Game.prototype.addMazeListener = function() {
365
366
// grab the map
367
368
let map = this.el.querySelector('.game-map');
369
370
// grab reference to game object since we are going into a function
371
// and "this" will no longer refer to the game object
372
373
let obj = this;
374
375
// if game board is clicked or tapped, see if we should change levels
376
map.addEventListener('mousedown',function(e) {
377
378
// if not at the goal, then get outta here
379
if (obj.player.y != obj.goal.y ||
380
obj.player.x != obj.goal.x) {
381
return;
382
}
383
// change level of game object by changing it's properties
384
obj.changeLevel();
385
386
// get the two layers
387
let layers = obj.el.querySelectorAll('.layer');
388
389
// clear tiles and sprites from layers
390
for (layer of layers) {
391
layer.innerHTML = '';
392
}
393
394
// place the new level.
395
obj.placeLevel();
396
397
// check the goal to reset the message.
398
obj.checkGoal();
399
400
});
401
};
402
403
/*
404
* Responds to a keydown event by moving the player and checking the goal.
405
*/
406
Game.prototype.keyboardListener = function() {
407
document.addEventListener('keydown', event => {
408
this.movePlayer(event);
409
this.checkGoal();
410
});
411
412
}
413
/*
414
* Adds mouse down listeners to buttons
415
*/
416
Game.prototype.buttonListeners = function() {
417
let up = document.getElementById('up');
418
let left = document.getElementById('left');
419
let down = document.getElementById('down')
420
let right = document.getElementById('right');
421
422
// the sprite is out of date
423
let obj = this;
424
up.addEventListener('mousedown',function() {
425
426
obj.moveUp();
427
obj.checkGoal();
428
});
429
down.addEventListener('mousedown',function() {
430
obj.moveDown();
431
obj.checkGoal();
432
});
433
left.addEventListener('mousedown',function() {
434
obj.moveLeft();
435
obj.checkGoal();
436
});
437
right.addEventListener('mousedown',function() {
438
obj.moveRight();
439
obj.checkGoal();
440
});
441
442
}
443
444
/*
445
* Sets the message of the text element.
446
* @param {String} msg - The message to be printed.
447
*/
448
Game.prototype.setMessage = function(msg) {
449
let text_el = this.el.querySelector('.text');
450
text_el.textContent = msg;
451
};
452
453
/*
454
* Sizes up the map based on array dimensions.
455
*/
456
Game.prototype.sizeUp = function() {
457
458
// inner container so that text can be below it
459
let map = this.el.querySelector('.game-map');
460
461
// inner container, height. Need this.map
462
map.style.height = this.map.length * this.tileDim + 'px';
463
464
map.style.width = this.map[0].length * this.tileDim + 'px';
465
466
};
467
468
469
/*
470
* Populates the map.
471
* Sizes up the map based on array dimensions.
472
* Gives the goal and player some references.
473
*/
474
Game.prototype.placeLevel = function() {
475
this.populateMap();
476
477
this.sizeUp();
478
479
this.placeSprite('goal');
480
481
// we want the DOM element that gets returned...
482
let playerSprite = this.placeSprite('player');
483
484
// ..so we can store it in the playerSprite element.
485
this.player.el = playerSprite;
486
487
}
488
/*
489
* Add keyboard, button, and maze tap listeners
490
*/
491
Game.prototype.addListeners = function() {
492
493
this.keyboardListener();
494
495
this.buttonListeners();
496
497
// changing levels
498
this.addMazeListener();
499
}
500
501
/*
502
* Initialization function called once
503
*/
504
context.init = function () {
505
506
let myGame = new Game('game-container-1',levels[0]);
507
508
// encapsulate for multi-level
509
myGame.placeLevel();
510
511
// add listeners
512
myGame.addListeners();
513
514
}
515
})(app);
516
517
/*
518
* Tell app to activate the init() function.
519
*/
520
521
app.init();
522
523
Any ideas please?
Advertisement
Answer
Just comment out the second and third level section of the levels[0] object (maps). Change the HTML content that makes reference to other levels.
JavaScript
1
521
521
1
let app = {};
2
3
4
(function(context) {
5
6
/*
7
* Build an array of levels.
8
* This will scale better if it is stored in a separate JSON File.
9
*/
10
let levels = [];
11
levels[0] = {
12
map:[
13
[0,0,1,1,1,0,1,1,0],
14
[1,0,1,1,0,0,0,0,0],
15
[0,0,1,1,0,1,1,0,1],
16
[1,0,0,0,0,0,1,0,1],
17
[1,1,1,1,1,0,1,0,1]
18
],
19
20
player:{
21
x:0,
22
y:0
23
},
24
goal:{
25
x:7,
26
y:4
27
},
28
theme:'default',
29
};
30
/* second level
31
levels[1] = {
32
map:[
33
[1,0,1,1,1,1],
34
[0,0,0,0,0,0],
35
[0,1,1,1,0,0],
36
[0,0,0,1,1,0],
37
[0,1,0,1,0,0]
38
],
39
theme:'grassland',
40
player:{
41
x:2,
42
y:4
43
},
44
goal:{
45
x:4,
46
y:4
47
}
48
};
49
// third level
50
levels[2] = {
51
map:[
52
[1,0,1,0,0,1,0],
53
[0,0,0,0,0,1,0],
54
[1,0,1,1,0,0,0],
55
[1,0,0,1,0,1,0],
56
[1,1,0,0,1,0,0]
57
],
58
theme:'dungeon',
59
player:{
60
x:2,
61
y:4
62
},
63
goal:{
64
x:6,
65
y:4
66
}
67
};
68
69
70
/*
71
* The game object constructor.
72
* @param {String} id - the id of the game container DOM element.
73
* @param {Object} level - the starting level of the game.
74
*/
75
function Game(id,level) {
76
77
this.el = document.getElementById(id);
78
79
// level addition
80
this.level_idx = 0;
81
82
// establish the basic properties common to all this objects.
83
this.tileTypes = ['floor','wall'];
84
this.tileDim = 32;
85
// inherit the level's properties: map, player start, goal start.
86
this.map = level.map;
87
88
// level switch
89
this.theme = level.theme;
90
91
// make a copy of the level's player.
92
this.player = {level.player};
93
94
// create a property for the DOM element, to be set later.
95
this.player.el = null;
96
97
// make a copy of the goal.
98
this.goal = {level.goal};
99
}
100
101
/*
102
* Create a tile or sprite <div> element.
103
* @param {Number} x - the horizontal coordinate the 2D array.
104
* @param {Number} y - the vertical coordinate in the 2D array.
105
*/
106
Game.prototype.createEl = function(x,y,type) {
107
// create one tile.
108
let el = document.createElement('div');
109
110
// two class names: one for tile, one or the tile type.
111
el.className = type;
112
113
// set width and height of tile based on the passed-in dimensions.
114
el.style.width = el.style.height = this.tileDim + 'px';
115
116
// set left positions based on x coordinate.
117
el.style.left = x*this.tileDim + 'px';
118
119
// set top position based on y coordinate.
120
el.style.top = y*this.tileDim + 'px';
121
122
return el;
123
}
124
125
/*
126
* Applies the level theme as a class to the game element.
127
* Populates the map by adding tiles and sprites to their respective layers.
128
*/
129
Game.prototype.populateMap = function() {
130
131
// add theme call
132
this.el.className = 'game-container ' + this.theme;
133
134
// make a reference to the tiles layer in the DOM.
135
let tiles = this.el.querySelector('#tiles');
136
137
// set up our loop to populate the grid.
138
for (var y = 0; y < this.map.length; ++y) {
139
for (var x = 0; x < this.map[y].length; ++x) {
140
141
let tileCode = this.map[y][x];
142
143
// determine tile type using code
144
// index into the tileTypes array using the code.
145
let tileType = this.tileTypes[tileCode];
146
147
// call the helper function
148
let tile = this.createEl(x,y,tileType);
149
150
// add to layer
151
tiles.appendChild(tile);
152
}
153
}
154
}
155
156
/*
157
* Place the player or goal sprite.
158
* @param {String} type - either 'player' or 'goal', used by createEl and becomes DOM ID
159
*/
160
Game.prototype.placeSprite = function(type) {
161
162
// syntactic sugar
163
let x = this[type].x
164
165
let y = this[type].y;
166
167
// reuse the createTile function
168
let sprite = this.createEl(x,y,type);
169
170
sprite.id = type;
171
172
// set the border radius of the sprite.
173
sprite.style.borderRadius = this.tileDim + 'px';
174
175
// get half the difference between tile and sprite.
176
177
// grab the layer
178
let layer = this.el.querySelector('#sprites');
179
180
layer.appendChild(sprite);
181
182
return sprite;
183
}
184
185
/*
186
* Triggers a collide animation on the player sprite.
187
*/
188
Game.prototype.collide = function() {
189
this.player.el.className += ' collide';
190
191
let obj = this;
192
193
window.setTimeout(function() {
194
obj.player.el.className = 'player';
195
},200);
196
197
return 0;
198
199
};
200
/*
201
* Moves the player sprite left.
202
*/
203
Game.prototype.moveLeft = function() {
204
// if at the boundary, return
205
if (this.player.x == 0) {
206
this.collide();
207
return;
208
}
209
// itentify next tile
210
let nextTile = this.map[this.player.y][this.player.x-1];
211
212
// if next tile is a wall, add collide effect and return
213
if (nextTile ==1) {
214
this.collide();
215
return;
216
}
217
// change coordinates of player object
218
this.player.x -=1;
219
// update location of DOM element
220
this.updateHoriz();
221
};
222
/*
223
* Moves the player sprite up.
224
*/
225
Game.prototype.moveUp = function() {
226
if (this.player.y == 0) {
227
// at end: these could be combined
228
this.collide();
229
return;
230
}
231
232
let nextTile = this.map[this.player.y-1][this.player.x];
233
if (nextTile ==1) {
234
this.collide();
235
return;
236
}
237
this.player.y -=1;
238
this.updateVert();
239
240
};
241
/*
242
* Moves the player sprite right.
243
*/
244
Game.prototype.moveRight = function() {
245
if (this.player.x == this.map[this.player.y].length-1) {
246
this.collide();
247
return;
248
}
249
nextTile = this.map[this.player.y][this.player.x+1];
250
251
if (nextTile ==1) {
252
this.collide()
253
return;
254
}
255
this.player.x += 1;
256
257
this.updateHoriz();
258
};
259
/*
260
* Moves player sprite down.
261
*/
262
Game.prototype.moveDown = function() {
263
if (this.player.y == this.map.length-1) {
264
this.collide();
265
return;
266
}
267
// find the next tile in the 2D array.
268
269
let nextTile = this.map[this.player.y+1][this.player.x];
270
if (nextTile ==1) {
271
this.collide()
272
return;
273
}
274
this.player.y += 1;
275
this.updateVert();
276
277
};
278
/*
279
* Updates vertical position of player sprite based on object's y coordinates.
280
*/
281
Game.prototype.updateVert = function() {
282
this.player.el.style.top = this.player.y * this.tileDim+ 'px';
283
};
284
/*
285
* Updates horizontal position of player sprite based on object's x coordinates.
286
*/
287
Game.prototype.updateHoriz = function() {
288
this.player.el.style.left = this.player.x * this.tileDim + 'px';
289
};
290
/*
291
* Moves player based on keyboard cursor presses.
292
*/
293
Game.prototype.movePlayer = function(event) {
294
event.preventDefault();
295
296
if (event.keyCode < 37 || event.keyCode > 40) {
297
return;
298
}
299
300
switch (event.keyCode) {
301
case 37:
302
this.moveLeft();
303
break;
304
305
case 38:
306
this.moveUp();
307
break;
308
309
case 39:
310
this.moveRight();
311
break;
312
313
case 40:
314
this.moveDown();
315
break;
316
}
317
}
318
/*
319
* Check on whether goal has been reached.
320
*/
321
Game.prototype.checkGoal = function() {
322
let body = document.querySelector('body');
323
324
if (this.player.y == this.goal.y &&
325
this.player.x == this.goal.x) {
326
327
body.className = 'success';
328
}
329
else {
330
body.className = '';
331
}
332
}
333
/*
334
* Changes the level of the game object.
335
*/
336
Game.prototype.changeLevel = function() {
337
338
// update the level index.
339
this.level_idx ++;
340
341
// if higher than max index, set back to zero.
342
if (this.level_idx > levels.length -1) {
343
this.level_idx = 0;
344
}
345
346
// get the level at this index.
347
let level = levels[this.level_idx];
348
349
// sync the map with the level map.
350
this.map = level.map;
351
// sync the theme with the level theme.
352
this.theme = level.theme;
353
354
// make a copy of the level's player object, since x and y change during the game.
355
this.player = {level.player};
356
357
// make a copy of the level's goal object, since x and y change between levels.
358
this.goal = {level.goal};
359
}
360
361
/*
362
* If goal has been reached,
363
*/
364
Game.prototype.addMazeListener = function() {
365
366
// grab the map
367
368
let map = this.el.querySelector('.game-map');
369
370
// grab reference to game object since we are going into a function
371
// and "this" will no longer refer to the game object
372
373
let obj = this;
374
375
// if game board is clicked or tapped, see if we should change levels
376
map.addEventListener('mousedown',function(e) {
377
378
// if not at the goal, then get outta here
379
if (obj.player.y != obj.goal.y ||
380
obj.player.x != obj.goal.x) {
381
return;
382
}
383
// change level of game object by changing it's properties
384
obj.changeLevel();
385
386
// get the two layers
387
let layers = obj.el.querySelectorAll('.layer');
388
389
// clear tiles and sprites from layers
390
for (layer of layers) {
391
layer.innerHTML = '';
392
}
393
394
// place the new level.
395
obj.placeLevel();
396
397
// check the goal to reset the message.
398
obj.checkGoal();
399
400
});
401
};
402
403
/*
404
* Responds to a keydown event by moving the player and checking the goal.
405
*/
406
Game.prototype.keyboardListener = function() {
407
document.addEventListener('keydown', event => {
408
this.movePlayer(event);
409
this.checkGoal();
410
});
411
412
}
413
/*
414
* Adds mouse down listeners to buttons
415
*/
416
Game.prototype.buttonListeners = function() {
417
let up = document.getElementById('up');
418
let left = document.getElementById('left');
419
let down = document.getElementById('down')
420
let right = document.getElementById('right');
421
422
// the sprite is out of date
423
let obj = this;
424
up.addEventListener('mousedown',function() {
425
426
obj.moveUp();
427
obj.checkGoal();
428
});
429
down.addEventListener('mousedown',function() {
430
obj.moveDown();
431
obj.checkGoal();
432
});
433
left.addEventListener('mousedown',function() {
434
obj.moveLeft();
435
obj.checkGoal();
436
});
437
right.addEventListener('mousedown',function() {
438
obj.moveRight();
439
obj.checkGoal();
440
});
441
442
}
443
444
/*
445
* Sets the message of the text element.
446
* @param {String} msg - The message to be printed.
447
*/
448
Game.prototype.setMessage = function(msg) {
449
let text_el = this.el.querySelector('.text');
450
text_el.textContent = msg;
451
};
452
453
/*
454
* Sizes up the map based on array dimensions.
455
*/
456
Game.prototype.sizeUp = function() {
457
458
// inner container so that text can be below it
459
let map = this.el.querySelector('.game-map');
460
461
// inner container, height. Need this.map
462
map.style.height = this.map.length * this.tileDim + 'px';
463
464
map.style.width = this.map[0].length * this.tileDim + 'px';
465
466
};
467
468
469
/*
470
* Populates the map.
471
* Sizes up the map based on array dimensions.
472
* Gives the goal and player some references.
473
*/
474
Game.prototype.placeLevel = function() {
475
this.populateMap();
476
477
this.sizeUp();
478
479
this.placeSprite('goal');
480
481
// we want the DOM element that gets returned...
482
let playerSprite = this.placeSprite('player');
483
484
// ..so we can store it in the playerSprite element.
485
this.player.el = playerSprite;
486
487
}
488
/*
489
* Add keyboard, button, and maze tap listeners
490
*/
491
Game.prototype.addListeners = function() {
492
493
this.keyboardListener();
494
495
this.buttonListeners();
496
497
// changing levels
498
this.addMazeListener();
499
}
500
501
/*
502
* Initialization function called once
503
*/
504
context.init = function () {
505
506
let myGame = new Game('game-container-1',levels[0]);
507
508
// encapsulate for multi-level
509
myGame.placeLevel();
510
511
// add listeners
512
myGame.addListeners();
513
514
}
515
})(app);
516
517
/*
518
* Tell app to activate the init() function.
519
*/
520
521
app.init();
JavaScript
1
235
235
1
/*
2
* General Styling
3
*/
4
body {
5
font-family: Calibri;
6
transition: 0.2s ease;
7
text-align: center;
8
}
9
body.success {
10
background-color: #b7f0b7;
11
transition: 0.2s ease;
12
}
13
/* center everything in game container */
14
.game-container {
15
margin: 0px auto;
16
}
17
/*
18
* Map screen
19
*/
20
21
.game-map {
22
position: relative;
23
}
24
/*
25
* Output text styles
26
*/
27
28
p {
29
margin: 10px 0px;
30
padding: 0px;
31
32
}
33
34
35
/*
36
* Map on left, controls on right
37
* Adapted for the mobile Medium app
38
*/
39
#map-and-controls {
40
display: flex;
41
justify-content: center;
42
}
43
/*
44
* Controls
45
*/
46
47
#controls {
48
margin-left: 10px;
49
text-align: center;
50
}
51
/*
52
* Container for right and left buttons
53
*/
54
#controls #horiz {
55
display: flex;
56
align-items: center;
57
justify-content: center;
58
}
59
/*
60
* General button styles
61
*/
62
#controls button {
63
padding: 10px 10px;
64
margin-top: 10px;
65
background-color: #DDD;
66
border: 1px solid #000;
67
width: 38px;
68
height: 38px;
69
border-radius: 3px;
70
cursor: pointer;
71
position: relative;
72
}
73
/*
74
* Spacing between horiz buttons
75
*/
76
button#right {
77
margin-left: 5px;
78
}
79
button#left {
80
margin-right: 5px;
81
}
82
83
/*
84
* General button arrow styles
85
*/
86
#controls button::before {
87
content:'';
88
width: 0px;
89
position: absolute;
90
}
91
/*
92
* Specific Arrow Styles
93
*/
94
button#left::before {
95
border-top: 10px solid transparent;
96
border-right: 15px solid #000;
97
border-bottom: 10px solid transparent;
98
left: 10px;
99
top: 9px;
100
}
101
button#right::before {
102
border-top: 10px solid transparent;
103
border-left: 15px solid #000;
104
border-bottom: 10px solid transparent;
105
left: 12px;
106
top: 9px;
107
}
108
button#up::before {
109
border-right: 10px solid transparent;
110
border-left: 10px solid transparent;
111
border-bottom: 15px solid #000;
112
left: 9px;
113
top: 9px;
114
}
115
button#down::before {
116
border-right: 10px solid transparent;
117
border-left: 10px solid transparent;
118
border-top: 15px solid #000;
119
left: 9px;
120
top: 12px;
121
}
122
#success-msg {
123
opacity: 0;
124
transition: opacity 0.2s ease;
125
position: absolute;
126
padding: 5px 5px;
127
background-color: rgba(0,0,0,0.5);
128
color: white;
129
width: calc(100% - 8px);
130
}
131
body.success #success-msg {
132
opacity: 1;
133
transition: opacity 0.2 ease;
134
}
135
136
137
138
/*
139
* Layers and tiles are positioned absolutely
140
* within coordinate system of .game-map
141
*/
142
div.layer,
143
div.layer div {
144
position: absolute;
145
}
146
/* border for floors and wall */
147
#tiles div {
148
border: 1px solid grey;
149
}
150
151
/*
152
* Default wall and floor styles
153
*/
154
155
.default .floor {
156
background-color: lightgrey;
157
}
158
159
.default .wall {
160
background-color: skyblue;
161
}
162
/*
163
* grassland theme
164
*/
165
.grassland .floor {
166
background-color: #7bb76d;
167
}
168
.grassland .wall {
169
background-color: #806d51;
170
}
171
.grassland #player {
172
background-color: #b2ccec;
173
}
174
175
/*
176
* dungeon theme
177
*/
178
.dungeon .floor {
179
background-color: darkgrey;
180
}
181
.dungeon .wall {
182
background-color: #9c649c;
183
}
184
.dungeon #player {
185
background-color: #ab1431;
186
}
187
/*
188
* player and goal are slightly smaller than tiles
189
*/
190
.player,
191
.goal {
192
transform-origin: center;
193
transform:scale(0.85);
194
}
195
/*
196
* Goal colors
197
*/
198
.goal {
199
background-color: #FFD700;
200
border: 1px solid #98720b;
201
}
202
/*
203
* Player default colors
204
*/
205
.player {
206
background-color: #90ee90;
207
border: 1px solid #008000;
208
transition: left 0.2s ease, top 0.2s ease;
209
}
210
/*
211
* Player wobbles when colliding with wall or border
212
*/
213
.player.collide {
214
animation: wobble 0.5s;
215
animation-iteration-count: infinite;
216
transition: background-color 0.2s;
217
218
}
219
220
/*
221
* Wobble animation
222
*/
223
@keyframes wobble {
224
0% { transform: scale(0.85) translate(1px, 1px); }
225
10% { transform: scale(0.85) translate(-1px, -2px); }
226
20% { transform: scale(0.85) translate(-3px, 0px); }
227
30% { transform: scale(0.85) translate(3px, 2px); }
228
40% { transform: scale(0.85) translate(1px, -1px);}
229
50% { transform: scale(0.85) translate(-1px, 2px); }
230
60% { transform: scale(0.85) translate(-3px, 1px); }
231
70% { transform: scale(0.85) translate(3px, 1px); }
232
80% { transform: scale(0.85) translate(-1px, -1px); }
233
90% { transform: scale(0.85) translate(1px, 2px); }
234
100% { transform: scale(0.85) translate(1px, -2px);; }
235
}
JavaScript
1
22
22
1
<div id="game-container-1" class="game-container">
2
3
<div id="map-and-controls">
4
<div id="game-map-1" class="game-map">
5
<div id="tiles" class="layer"></div>
6
<div id="sprites" class="layer"></div>
7
<div id="success-msg">Goal reached! Tap the maze to play again.</div>
8
</div>
9
10
<!-- controls-->
11
<div id="controls">
12
<button id="up"></button>
13
<div id="horiz">
14
<button id="left"></button>
15
<button id="right"></button>
16
</div>
17
<button id="down"></button>
18
</div>
19
</div>
20
<p id="text-1" class="text">Use cursor keys or buttons to move the marble.</p>
21
22
</div>