Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
AroriaNetwork
GitHub Repository: AroriaNetwork/3kho-backup
Path: blob/main/projects/missiles/src/game.js
1835 views
1
MG.game = (function () {
2
/** Constants **/
3
var GameState = {
4
WAIT_START: 'wait_start',
5
STARTING: 'starting',
6
RUNNING: 'running',
7
FINISHED: 'finished',
8
CRASHED: 'crashed'
9
}
10
11
var STARTING_LIVES = 5;
12
13
var LEVEL_NUM_BARRIERS = 20;
14
15
/** Variables **/
16
var mState = GameState.WAIT_START;
17
18
var mLives = STARTING_LIVES;
19
var mLevel = 0;
20
21
var mRemainingBarriers = 0;
22
var mBarriersToPass = 0;
23
24
var mProgress = 0.0;
25
var mBestProgress = 0.0;
26
27
/* Strings for UI ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
28
var getLevelString = function () {
29
return mLevel ? 'LEVEL ' + mLevel : 'QUALIFYING LEVEL';
30
}
31
32
var Messages = {
33
START: {
34
title: getLevelString,
35
text: function () {return 'CLICK TO BEGIN';}
36
},
37
CRASH: {
38
title: function () {return 'CRASHED';},
39
text: function () {return 'CLICK TO RETRY';}
40
},
41
GAME_OVER: {
42
title: function () {return 'GAME OVER';},
43
text: function () {return 'CLICK TO START AGAIN';}
44
},
45
FINISH: {
46
title: function () {return 'LEVEL COMPLETED';},
47
text: function () {return 'CLICK TO CONTINUE';}
48
}
49
};
50
51
52
53
var getLevelStartVelocity = function (level) {
54
return 300 + 100*level;
55
}
56
57
var getLevelFinishVelocity = function (level) {
58
return 400 + 100*level;
59
}
60
61
var getPreLevelIdleVelocity = function (level) {
62
return 350 + 100*level;
63
}
64
65
var getPostLevelIdleVelocity = function (level) {
66
return 550 + 100*level;
67
}
68
69
var playCrashAnimation = function () {
70
// TODO move drawing out of the update loop
71
72
// create a copy of the explosion element
73
var explosion = document.getElementById('explosion');
74
75
// play the animation
76
explosion.firstChild.beginElement();
77
explosion.setAttribute('visibility', 'visible');
78
79
// TODO can't seem to get a callback to fire when the animation
80
// finishes. Use timeout instead
81
setTimeout(function (){
82
var explosion = document.getElementById('explosion');
83
explosion.setAttribute('visibility', 'hidden');
84
}, 400);
85
}
86
87
var goWaitStartLevel = function () {
88
MG.banner.show(Messages.START.title(), Messages.START.text());
89
MG.util.showMouse();
90
91
MG.missile.setAutopilot();
92
MG.missile.setVelocity(getPreLevelIdleVelocity(mLevel));
93
94
if (mLevel === 0) {mLives = Infinity;}
95
96
mState = GameState.WAIT_START;
97
}
98
99
/**
100
*
101
*/
102
var goRun = function () {
103
MG.banner.hide();
104
MG.util.hideMouse();
105
106
/* TODO should the start barrier be pushed here?
107
If so, should all of the barriers for the entire level be pushed as well? */
108
mRemainingBarriers = LEVEL_NUM_BARRIERS;
109
MG.barrierQueue.pushBarrier(MG.BarrierType.START);
110
111
mBarriersToPass = LEVEL_NUM_BARRIERS;
112
113
MG.missile.setManual();
114
115
mState = GameState.STARTING;
116
}
117
118
var goFinish = function () {
119
MG.banner.show(Messages.FINISH.title(), Messages.FINISH.text());
120
MG.util.showMouse();
121
122
MG.missile.setAutopilot();
123
MG.missile.setVelocity(getPostLevelIdleVelocity(mLevel));
124
125
mState = GameState.FINISHED;
126
}
127
128
var goCrash = function () {
129
MG.util.showMouse();
130
131
if (mLives === 0) {
132
MG.banner.show(Messages.GAME_OVER.title(), Messages.GAME_OVER.text());
133
} else {
134
MG.banner.show(Messages.CRASH.title(), Messages.CRASH.text());
135
}
136
137
playCrashAnimation()
138
139
mState = GameState.CRASHED;
140
141
}
142
143
144
//==========================================================================
145
146
return {
147
init: function () {
148
var rootNode = document.getElementById('tunnel');
149
150
MG.missile.init();
151
152
//
153
154
var wallNode;
155
156
wallNode = document.createElementNS(NAMESPACE_SVG, 'g');
157
wallNode.setAttribute('transform', 'scale(1,-1)');
158
159
MG.tunnelWall.init(wallNode);
160
161
rootNode.appendChild(wallNode);
162
163
//
164
165
var barrierQueueNode;
166
167
barrierQueueNode = document.createElementNS(NAMESPACE_SVG, 'g');
168
barrierQueueNode.setAttribute('transform', 'scale(1,-1)');
169
170
MG.barrierQueue.init(barrierQueueNode);
171
172
rootNode.appendChild(barrierQueueNode);
173
174
//
175
176
goWaitStartLevel();
177
178
rootNode.setAttribute('visibility', 'visible');
179
},
180
181
182
update: function (dt) {
183
MG.missile.update(dt);
184
MG.tunnelWall.update(dt);
185
MG.barrierQueue.update(dt);
186
187
/* check whether the nearest barrier has been reached and whether the missile collides with it. */
188
if (!MG.barrierQueue.isEmpty()) {
189
if (MG.missile.getOffset() < MG.MISSILE_LENGTH && !MG.missile.isCrashed()){
190
var barrier = MG.barrierQueue.nextBarrier();
191
192
if (barrier.collides(MG.missile.getPosition().x, MG.missile.getPosition().y)) {
193
// CRASH
194
MG.missile.onCrash();
195
goCrash();
196
} else {
197
198
// BARRIER PASSED
199
MG.barrierQueue.popBarrier();
200
MG.missile.onBarrierPassed();
201
202
// TODO this block makes loads of assumptions about state
203
if (mState === GameState.RUNNING
204
|| mState === GameState.STARTING) {
205
switch(barrier.getType()) {
206
case MG.BarrierType.FINISH:
207
goFinish();
208
break;
209
case MG.BarrierType.BLANK:
210
break;
211
case MG.BarrierType.START:
212
mState = GameState.RUNNING;
213
// FALLTHROUGH
214
default:
215
mBarriersToPass--;
216
217
var startVelocity = getLevelStartVelocity(mLevel);
218
var finishVelocity = getLevelFinishVelocity(mLevel);
219
220
MG.missile.setVelocity(startVelocity
221
+ (startVelocity - finishVelocity)
222
* (mBarriersToPass - LEVEL_NUM_BARRIERS)
223
/ LEVEL_NUM_BARRIERS);
224
break;
225
}
226
}
227
}
228
}
229
}
230
231
232
/* Pad the barrier queue with blank barriers so that there are barriers
233
as far as can be seen. */
234
while (MG.barrierQueue.numBarriers() < MG.LINE_OF_SIGHT/MG.BARRIER_SPACING) {
235
var type = MG.BarrierType.BLANK;
236
237
if (mState === GameState.RUNNING
238
|| mState === GameState.STARTING) {
239
mRemainingBarriers--;
240
if (mRemainingBarriers > 0) {
241
type = MG.BarrierType.RANDOM;
242
} else if (mRemainingBarriers === 0) {
243
type = MG.BarrierType.FINISH;
244
} else {
245
type = MG.BarrierType.BLANK;
246
}
247
}
248
249
MG.barrierQueue.pushBarrier(type);
250
}
251
252
/* Update progress */
253
switch (mState) {
254
case GameState.RUNNING:
255
mProgress = 1 - (mBarriersToPass*MG.BARRIER_SPACING + MG.missile.getOffset())/(LEVEL_NUM_BARRIERS * MG.BARRIER_SPACING);
256
mBestProgress = Math.max(mProgress, mBestProgress);
257
break;
258
case GameState.FINISHED:
259
mProgress = 1;
260
mBestProgress = 1;
261
break;
262
case GameState.STARTING:
263
mProgress = 0;
264
break;
265
default:
266
break;
267
}
268
269
},
270
271
updateDOM: function () {
272
var position = MG.missile.getPosition();
273
var offset = MG.missile.getOffset();
274
275
MG.barrierQueue.updateDOM(-position.x, -position.y, offset);
276
MG.tunnelWall.updateDOM(-position.x, -position.y, offset);
277
},
278
279
onMouseMove: function (x, y) {
280
var windowWidth = window.innerWidth;
281
var windowHeight = window.innerHeight;
282
283
MG.missile.setTarget(x - 0.5*windowWidth, -(y - 0.5*windowHeight));
284
285
},
286
287
onMouseClick: function () {
288
if (MG.banner.isFullyVisible()) {
289
switch (mState) {
290
case GameState.WAIT_START:
291
goRun();
292
break;
293
case GameState.FINISHED:
294
/* The player is given an infinite number of lives
295
during the qualifying level but these should be
296
removed before continuing. */
297
if (mLevel === 0) {mLives = STARTING_LIVES;}
298
299
mLevel++;
300
301
mBestProgress = 0.0;
302
303
goWaitStartLevel();
304
break;
305
case GameState.CRASHED:
306
MG.banner.hide();
307
MG.fog.fadeIn(function() {
308
if (mLives === 0) {
309
mLevel = 0;
310
mLives = STARTING_LIVES;
311
mBestProgress = 0.0;
312
} else {
313
mLives--;
314
}
315
316
317
MG.missile.reset();
318
MG.barrierQueue.reset();
319
320
MG.fog.fadeOut();
321
goWaitStartLevel();
322
});
323
break;
324
}
325
}
326
},
327
328
/* Returns an integer representing the current level */
329
getLevel: function () {
330
return mLevel;
331
},
332
333
/* Returns a human readable string describing the current level */
334
getLevelString: getLevelString,
335
336
/* Returns the number of times the player can crash before game over. */
337
/* If the player crashes with zero lives remaining the game ends */
338
getNumLives: function () {
339
return mLives;
340
},
341
342
/* Returns the progress through the level as a value between 0 and 1,
343
where 0 is not yet started and 1 is completed. */
344
getProgress: function () {
345
return mProgress;
346
},
347
348
getBestProgress: function () {
349
return mBestProgress;
350
}
351
};
352
353
}());
354
355