Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(454)

Side by Side Diff: chrome/renderer/resources/offline.js

Issue 545973003: Update network error template to new design (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: More JS style changes Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 (function() {
5 'use strict';
6 /**
7 * T-Rex runner.
8 * @param {string} outerContainerId Outer containing element id.
9 * @param {object} opt_config
10 * @constructor
11 * @export
12 */
13 function Runner(outerContainerId, opt_config) {
14 // Singleton
15 if (Runner.instance_) {
16 return Runner.instance_;
17 }
18 Runner.instance_ = this;
19
20 this.outerContainerEl = document.querySelector(outerContainerId);
21 this.containerEl = null;
22
23 this.config = opt_config || Runner.config;
24
25 this.dimensions = Runner.defaultDimensions;
26
27 this.canvas = null;
28 this.canvasCtx = null;
29
30 this.tRex = null;
31
32 this.distanceMeter = null;
33 this.distanceRan = 0;
34
35 this.highestScore = 0;
36
37 this.time = 0;
38 this.runningTime = 0;
39 this.msPerFrame = 1000 / FPS;
40 this.currentSpeed = this.config.SPEED;
41
42 this.obstacles = [];
43
44 this.started = false;
45 this.activated = false;
46 this.crashed = false;
47 this.paused = false;
48
49 this.resizeTimerId_ = null;
50
51 this.playCount = 0;
52
53 // Sound FX.
54 this.audioBuffer = null;
55 this.soundFx = {};
56
57 // Global web audio context for playing sounds.
58 this.audioContext = new AudioContext();
59
60 // Images.
61 this.images = {};
62 this.imagesLoaded = 0;
63 this.loadImages();
64 }
65 window['Runner'] = Runner;
66
67
68 /**
69 * Default game width.
70 * @const
71 */
72 var DEFAULT_WIDTH = 600;
73
74 /**
75 * Frames per second.
76 * @const
77 */
78 var FPS = 60;
79
80 /** @const */
81 var ISHDPI = window.devicePixelRatio > 1;
82
83 /** @const */
84 var ISMOBILE = window.navigator.userAgent.indexOf('Mobi') > -1;
85
86 /** @const */
87 var ISTOUCHENABLED = 'ontouchstart' in window;
88
89
90 /**
91 * Default game configuration.
92 * @enum {number}
93 */
94 Runner.config = {
95 ACCELERATION: 0.001,
96 BG_CLOUD_SPEED: 0.2,
97 BOTTOM_PAD: 10,
98 CLEAR_TIME: 3000,
99 CLOUD_FREQUENCY: 0.5,
100 GAMEOVER_CLEAR_TIME: 750,
101 GAP_COEFFICIENT: 0.6,
102 GRAVITY: 0.6,
103 INITIAL_JUMP_VELOCITY: 12,
104 MAX_CLOUDS: 6,
105 MAX_OBSTACLE_LENGTH: 3,
106 MAX_SPEED: 12,
107 MIN_JUMP_HEIGHT: 35,
108 MOBILE_SPEED_COEFFICIENT: 1.2,
109 RESOURCE_TEMPLATE_ID: 'audio-resources',
110 SPEED: 6,
111 SPEED_DROP_COEFFICIENT: 3
112 };
113
114
115 /**
116 * Default dimensions.
117 * @enum {string}
118 */
119 Runner.defaultDimensions = {
120 WIDTH: DEFAULT_WIDTH,
121 HEIGHT: 150
122 };
123
124
125 /**
126 * CSS class names.
127 * @enum {string}
128 */
129 Runner.classes = {
130 CANVAS: 'runner-canvas',
131 CONTAINER: 'runner-container',
132 CRASHED: 'crashed',
133 ICON: 'icon-offline',
134 TOUCH_CONTROLLER: 'controller'
135 };
136
137
138 /**
139 * Image source urls.
140 * @enum {array.<object>}
141 */
142 Runner.imageSources = {
143 LDPI: [
144 {name: 'CACTUS_LARGE', id: '1x-obstacle-large'},
145 {name: 'CACTUS_SMALL', id: '1x-obstacle-small'},
146 {name: 'CLOUD', id: '1x-cloud'},
147 {name: 'HORIZON', id: '1x-horizon'},
148 {name: 'RESTART', id: '1x-restart'},
149 {name: 'TEXT_SPRITE', id: '1x-text'},
150 {name: 'TREX', id: '1x-trex'}
151 ],
152 HDPI: [
153 {name: 'CACTUS_LARGE', id: '2x-obstacle-large'},
154 {name: 'CACTUS_SMALL', id: '2x-obstacle-small'},
155 {name: 'CLOUD', id: '2x-cloud'},
156 {name: 'HORIZON', id: '2x-horizon'},
157 {name: 'RESTART', id: '2x-restart'},
158 {name: 'TEXT_SPRITE', id: '2x-text'},
159 {name: 'TREX', id: '2x-trex'}
160 ]
161 };
162
163
164 /**
165 * Sound FX. Reference to the ID of the audio tag on interstitial page.
166 * @enum {string}
167 */
168 Runner.sounds = {
169 BUTTON_PRESS: 'offline-sound-press',
170 HIT: 'offline-sound-hit',
171 SCORE: 'offline-sound-reached'
172 };
173
174
175 /**
176 * Key code mapping.
177 * @enum {object}
178 */
179 Runner.keycodes = {
180 JUMP: {'38': 1, '32': 1}, // Up, spacebar
181 DUCK: {'40': 1}, // Down
182 RESTART: {'13': 1} // Enter
183 };
184
185
186 /**
187 * Runner event names.
188 * @enum {string}
189 */
190 Runner.events = {
191 ANIM_END: 'webkitAnimationEnd',
192 CLICK: 'click',
193 KEYDOWN: 'keydown',
194 KEYUP: 'keyup',
195 MOUSEDOWN: 'mousedown',
196 MOUSEUP: 'mouseup',
197 RESIZE: 'resize',
198 TOUCHEND: 'touchend',
199 TOUCHSTART: 'touchstart',
200 VISIBILITY: 'visibilitychange',
201 BLUR: 'blur',
202 FOCUS: 'focus',
203 LOAD: 'load'
204 };
205
206
207 Runner.prototype = {
208 /**
209 * Setting individual settings for debugging.
210 * @param {string} setting
211 * @param {*} value
212 */
213 updateConfigSetting: function(setting, value) {
214 if (setting in this.config && value != undefined) {
215 this.config[setting] = value;
216
217 switch (setting) {
218 case 'GRAVITY':
219 case 'MIN_JUMP_HEIGHT':
220 case 'SPEED_DROP_COEFFICIENT':
221 this.tRex.config[setting] = value;
222 break;
223 case 'INITIAL_JUMP_VELOCITY':
224 this.tRex.setJumpVelocity(value);
225 break;
226 case 'SPEED':
227 this.setSpeed(value);
228 break;
229 }
230 }
231 },
232
233 /**
234 * Load and cache the image assets from the page.
235 */
236 loadImages: function() {
237 var imageSources = ISHDPI ? Runner.imageSources.HDPI :
238 Runner.imageSources.LDPI;
239
240 var numImages = imageSources.length;
241
242 for (var i = numImages - 1; i >= 0; i--) {
243 var imgSource = imageSources[i];
244 this.images[imgSource.name] = document.getElementById(imgSource.id);
245 }
246 this.init();
247 },
248
249 /**
250 * Load and decode base 64 encoded sounds.
251 */
252 loadSounds: function() {
253 var resourceTemplate =
254 document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content;
255
256 for (var sound in Runner.sounds) {
257 var soundSrc = resourceTemplate.getElementById(Runner.sounds[sound]).src;
258 soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);
259 var buffer = Runner.decodeBase64ToArrayBuffer(soundSrc);
260
261 // Async, so no guarantee of order in array.
262 this.audioContext.decodeAudioData(buffer, function(index, audioData) {
263 this.soundFx[index] = audioData;
264 }.bind(this, sound));
265 }
266 },
267
268 /**
269 * Sets the game speed. Adjust the speed accordingly if on a smaller screen.
270 * @param {number} opt_speed
271 */
272 setSpeed: function(opt_speed) {
273 var speed = opt_speed || this.currentSpeed;
274
275 // Reduce the speed on smaller mobile screens.
276 if (this.dimensions.WIDTH < DEFAULT_WIDTH) {
277 var mobileSpeed = speed * this.dimensions.WIDTH / DEFAULT_WIDTH *
278 this.config.MOBILE_SPEED_COEFFICIENT;
279 this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed;
280 } else if (opt_speed) {
281 this.currentSpeed = opt_speed;
282 }
283 },
284
285 /**
286 * Game initialiser.
287 */
288 init: function() {
289 // Hide the static icon.
290 document.querySelector('.' + Runner.classes.ICON).style.visibility =
291 'hidden';
292
293 this.adjustDimensions();
294 this.setSpeed();
295
296 this.containerEl = document.createElement('div');
297 this.containerEl.className = Runner.classes.CONTAINER;
298
299 // Player canvas container.
300 this.canvas = Runner.createCanvas(this.containerEl, this.dimensions.WIDTH,
301 this.dimensions.HEIGHT, Runner.classes.PLAYER);
302
303 this.canvasCtx = this.canvas.getContext('2d');
304 this.canvasCtx.fillStyle = '#f7f7f7';
305 this.canvasCtx.fill();
306 Runner.updateCanvasScaling(this.canvas);
307
308 // Horizon contains clouds, obstacles and the ground.
309 this.horizon = new Horizon(this.canvas, this.images, this.dimensions,
310 this.config.GAP_COEFFICIENT);
311
312 // Distance meter
313 this.distanceMeter = new DistanceMeter(this.canvas,
314 this.images.TEXT_SPRITE, this.dimensions.WIDTH);
315
316 // Draw t-rex
317 this.tRex = new Trex(this.canvas, this.images.TREX);
318
319 this.outerContainerEl.appendChild(this.containerEl);
320
321 if (ISMOBILE) {
322 this.createTouchController();
323 }
324
325 this.startListening();
326 this.update();
327 this.loadSounds();
328
329 window.addEventListener(Runner.events.RESIZE,
330 this.debounceResize.bind(this), false);
331 },
332
333 /**
334 * Create the touch controller. A div that covers whole screen.
335 */
336 createTouchController: function() {
337 this.touchController = document.createElement('div');
338 this.touchController.className = Runner.classes.TOUCH_CONTROLLER;
339 },
340
341 /**
342 * Debounce the resize event.
343 */
344 debounceResize: function() {
345 if (!this.resizeTimerId_) {
346 this.resizeTimerId_ =
347 setInterval(this.adjustDimensions.bind(this), 250);
348 }
349 },
350
351 /**
352 * Adjust game space dimensions on resize.
353 */
354 adjustDimensions: function() {
355 clearInterval(this.resizeTimerId_);
356 this.resizeTimerId_ = null;
357
358 var boxStyles = window.getComputedStyle(this.outerContainerEl);
359 var padding = Number(boxStyles.paddingLeft.substr(0,
360 boxStyles.paddingLeft.length - 2));
361
362 this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2;
363
364 // Redraw the elements back onto the canvas.
365 if (this.canvas) {
366 this.canvas.width = this.dimensions.WIDTH;
367 this.canvas.height = this.dimensions.HEIGHT;
368
369 Runner.updateCanvasScaling(this.canvas);
370
371 this.distanceMeter.calcXPos(this.dimensions.WIDTH);
372 this.clearCanvas();
373 this.horizon.update(0, 0, true);
374 this.tRex.update(0);
375
376 // Outer container and distance meter.
377 if (this.activated || this.crashed) {
378 this.containerEl.style.width = this.dimensions.WIDTH + 'px';
379 this.containerEl.style.height = this.dimensions.HEIGHT + 'px';
380 this.distanceMeter.update(0, Math.ceil(this.distanceRan));
381 this.stop();
382 } else {
383 this.tRex.draw(0, 0);
384 }
385
386 // Game over panel.
387 if (this.crashed && this.gameOverPanel) {
388 this.gameOverPanel.updateDimensions(this.dimensions.WIDTH);
389 this.gameOverPanel.draw();
390 }
391 }
392 },
393
394 /**
395 * Play the game intro.
396 * Canvas container width expands out to the full width.
397 */
398 playIntro: function() {
399 if (!this.started && !this.crashed) {
400 this.playingIntro = true;
401 this.tRex.playingIntro = true;
402
403 // CSS animation definition.
404 var keyframes = '@-webkit-keyframes intro { ' +
405 'from { width:' + Trex.config.WIDTH + 'px }' +
406 'to { width: ' + this.dimensions.WIDTH + 'px }' +
407 '}';
408 document.styleSheets[0].insertRule(keyframes, 0);
409
410 this.containerEl.addEventListener(Runner.events.ANIM_END,
411 this.startGame.bind(this));
412
413 this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
414 this.containerEl.style.width = this.dimensions.WIDTH + 'px';
415
416 if (this.touchController) {
417 this.outerContainerEl.appendChild(this.touchController);
418 }
419 this.activated = true;
420 this.started = true;
421 } else if (this.crashed) {
422 this.restart();
423 }
424 },
425
426
427 /**
428 * Update the game status to started.
429 */
430 startGame: function() {
431 this.runningTime = 0;
432 this.playingIntro = false;
433 this.tRex.playingIntro = false;
434 this.containerEl.style.webkitAnimation = '';
435 this.playCount++;
436
437 // Handle tabbing off the page. Pause the current game.
438 window.addEventListener(Runner.events.VISIBILITY,
439 this.onVisibilityChange.bind(this), false);
440
441 window.addEventListener(Runner.events.BLUR,
442 this.onVisibilityChange.bind(this), false);
443
444 window.addEventListener(Runner.events.FOCUS,
445 this.onVisibilityChange.bind(this), false);
446 },
447
448 clearCanvas: function() {
449 this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH,
450 this.dimensions.HEIGHT);
451 },
452
453 /**
454 * Update the game frame.
455 */
456 update: function() {
457 this.drawPending = false;
458
459 var now = performance.now();
460 var deltaTime = now - (this.time || now);
461 this.time = now;
462
463 if (this.activated) {
464 this.clearCanvas();
465
466 if (this.tRex.jumping) {
467 this.tRex.updateJump(deltaTime, this.config);
468 }
469
470 this.runningTime += deltaTime;
471 var hasObstacles = this.runningTime > this.config.CLEAR_TIME;
472
473 // First jump triggers the intro.
474 if (this.tRex.jumpCount == 1 && !this.playingIntro) {
475 this.playIntro();
476 }
477
478 // The horizon doesn't move until the intro is over.
479 if (this.playingIntro) {
480 this.horizon.update(0, this.currentSpeed, hasObstacles);
481 } else {
482 deltaTime = !this.started ? 0 : deltaTime;
483 this.horizon.update(deltaTime, this.currentSpeed, hasObstacles);
484 }
485
486 // Check for collisions.
487 var collision = hasObstacles &&
488 Runner.Collision.check(this.horizon.obstacles[0], this.tRex);
489
490 if (!collision) {
491 this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;
492
493 if (this.currentSpeed < this.config.MAX_SPEED) {
494 this.currentSpeed += this.config.ACCELERATION;
495 }
496 } else {
497 this.gameOver();
498 }
499
500 if (this.distanceMeter.getActualDistance(this.distanceRan) >
501 this.distanceMeter.maxScore) {
502 this.distanceRan = 0;
503 }
504
505 var playAcheivementSound = this.distanceMeter.update(deltaTime,
506 Math.ceil(this.distanceRan));
507
508 if (playAcheivementSound) {
509 this.playSound(this.soundFx.SCORE);
510 }
511 }
512
513 if (!this.crashed) {
514 this.tRex.update(deltaTime);
515 this.raq();
516 }
517 },
518
519 /**
520 * Draw the collision boxes on canvas, for debugging.
521 * @param {Runner.CollisionBox} collision T-Rex collison box.
522 * @param {Runner.CollisionBox} obstacle Obstacle collison box.
523 */
524 drawCollisionBoxes: function(collision, obstacle) {
525 this.canvasCtx.save();
526 this.canvasCtx.strokeStyle = '#f00';
527 this.canvasCtx.strokeRect(collision[0].x, collision[0].y,
528 collision[0].width, collision[0].height);
529
530 this.canvasCtx.strokeStyle = '#00f';
531 this.canvasCtx.strokeRect(collision[1].x, collision[1].y,
532 collision[1].width, collision[1].height);
533 this.canvasCtx.restore();
534 },
535
536 /**
537 * Event handler.
538 */
539 handleEvent: function(e) {
540 return (function(evtType, events) {
541 switch (evtType) {
542 case events.KEYDOWN:
543 case events.TOUCHSTART:
544 case events.MOUSEDOWN:
545 this.onKeyDown(e);
546 break;
547 case events.KEYUP:
548 case events.TOUCHEND:
549 case events.MOUSEUP:
550 this.onKeyUp(e);
551 break;
552 }
553 }.bind(this))(e.type, Runner.events);
554 },
555
556 /**
557 * Bind relevant key / mouse / touch listeners.
558 */
559 startListening: function() {
560 // Keys.
561 document.addEventListener(Runner.events.KEYDOWN, this);
562 document.addEventListener(Runner.events.KEYUP, this);
563
564 if (ISMOBILE) {
565 // Mobile only touch devices.
566 this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
567 this.touchController.addEventListener(Runner.events.TOUCHEND, this);
568 this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);
569 } else {
570 // Mouse.
571 document.addEventListener(Runner.events.MOUSEDOWN, this);
572 document.addEventListener(Runner.events.MOUSEUP, this);
573 }
574 },
575
576 /**
577 * Remove all listeners.
578 */
579 stopListening: function() {
580 document.removeEventListener(Runner.events.KEYDOWN, this);
581 document.removeEventListener(Runner.events.KEYUP, this);
582
583 if (ISMOBILE) {
584 this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);
585 this.touchController.removeEventListener(Runner.events.TOUCHEND, this);
586 this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);
587 } else {
588 document.removeEventListener(Runner.events.MOUSEDOWN, this);
589 document.removeEventListener(Runner.events.MOUSEUP, this);
590 }
591 },
592
593 /**
594 * Process keydown.
595 * @param {Event} e
596 */
597 onKeyDown: function(e) {
598 if (!this.crashed && (Runner.keycodes.JUMP[String(e.keyCode)] ||
599 e.type == Runner.events.TOUCHSTART)) {
600 if (!this.activated) {
601 this.activated = true;
602 }
603
604 if (!this.tRex.jumping) {
605 this.playSound(this.soundFx.BUTTON_PRESS);
606 this.tRex.startJump();
607 }
608 }
609
610 if (this.crashed && e.type == Runner.events.TOUCHSTART &&
611 e.currentTarget == this.containerEl) {
612 this.restart();
613 }
614
615 // Speed drop, activated only when jump key is not pressed.
616 if (Runner.keycodes.DUCK[e.keyCode] && this.tRex.jumping) {
617 e.preventDefault();
618 this.tRex.setSpeedDrop();
619 }
620 },
621
622
623 /**
624 * Process key up.
625 * @param {Event} e
626 */
627 onKeyUp: function(e) {
628 var keyCode = String(e.keyCode);
629 var isjumpKey = Runner.keycodes.JUMP[keyCode] ||
630 e.type == Runner.events.TOUCHEND ||
631 e.type == Runner.events.MOUSEDOWN;
632
633 if (this.isRunning() && isjumpKey) {
634 this.tRex.endJump();
635 } else if (Runner.keycodes.DUCK[keyCode]) {
636 this.tRex.speedDrop = false;
637 } else if (this.crashed) {
638 // Check that enough time has elapsed before allowing jump key to restart.
639 var deltaTime = performance.now() - this.time;
640
641 if (Runner.keycodes.RESTART[keyCode] ||
642 (e.type == Runner.events.MOUSEUP && e.target == this.canvas) ||
643 (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&
644 Runner.keycodes.JUMP[keyCode])) {
645 this.restart();
646 }
647 } else if (this.paused && isjumpKey) {
648 this.play();
649 }
650 },
651
652 /**
653 * RequestAnimationFrame wrapper.
654 */
655 raq: function() {
656 if (!this.drawPending) {
657 this.drawPending = true;
658 this.raqId = requestAnimationFrame(this.update.bind(this));
659 }
660 },
661
662 /**
663 * Whether the game is running.
664 * @return {boolean}
665 */
666 isRunning: function() {
667 return !!this.raqId;
668 },
669
670 /**
671 * Game over state.
672 */
673 gameOver: function() {
674 this.playSound(this.soundFx.HIT);
675 Runner.vibrate(200);
676
677 this.stop();
678 this.crashed = true;
679 this.distanceMeter.acheivement = false;
680
681 this.tRex.update(100, Trex.status.CRASHED);
682
683 // Game over panel.
684 if (!this.gameOverPanel) {
685 this.gameOverPanel = new Runner.GameOverPanel(this.canvas,
686 this.images.TEXT_SPRITE, this.images.RESTART,
687 this.dimensions);
688 } else {
689 this.gameOverPanel.draw();
690 }
691
692 // Update the high score.
693 if (this.distanceRan > this.highestScore) {
694 this.highestScore = Math.ceil(this.distanceRan);
695 this.distanceMeter.setHighScore(this.highestScore);
696 }
697
698 // Reset the time clock.
699 this.time = performance.now();
700 },
701
702 stop: function() {
703 this.activated = false;
704 this.paused = true;
705 cancelAnimationFrame(this.raqId);
706 this.raqId = 0;
707 },
708
709 play: function() {
710 if (!this.crashed) {
711 this.activated = true;
712 this.paused = false;
713 this.tRex.update(0, Trex.status.RUNNING);
714 this.time = performance.now();
715 this.update();
716 }
717 },
718
719 restart: function() {
720 if (!this.raqId) {
721 this.playCount++;
722 this.runningTime = 0;
723 this.activated = true;
724 this.crashed = false;
725 this.distanceRan = 0;
726 this.setSpeed(this.config.SPEED);
727
728 this.time = performance.now();
729 this.containerEl.classList.remove(Runner.classes.CRASHED);
730 this.clearCanvas();
731 this.distanceMeter.reset(this.highestScore);
732 this.horizon.reset();
733 this.tRex.reset();
734 this.playSound(this.soundFx.BUTTON_PRESS);
735
736 this.update();
737 }
738 },
739
740 /**
741 * Pause the game if the tab is not in focus.
742 */
743 onVisibilityChange: function(e) {
744 if (document.hidden || document.webkitHidden || e.type == 'blur') {
745 this.stop();
746 } else {
747 this.play();
748 }
749 },
750
751 /**
752 * Play a sound.
753 * @param {SoundBuffer} soundBuffer
754 */
755 playSound: function(soundBuffer) {
756 if (soundBuffer) {
757 var sourceNode = this.audioContext.createBufferSource();
758 sourceNode.buffer = soundBuffer;
759 sourceNode.connect(this.audioContext.destination);
760 sourceNode.start(0);
761 }
762 }
763 };
764
765
766 /**
767 * Updates the canvas size taking into
768 * account the backing store pixel ratio and
769 * the device pixel ratio.
770 *
771 * See article by Paul Lewis:
772 * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
773 *
774 * @param {HTMLCanvasElement} canvas
775 * @param {number} opt_width
776 * @param {number} opt_height
777 * @return {boolean} Whether the canvas was scaled.
778 */
779 Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) {
780 var context = canvas.getContext('2d');
781
782 // Query the various pixel ratios
783 var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;
784 var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1;
785 var ratio = devicePixelRatio / backingStoreRatio;
786
787 // Upscale the canvas if the two ratios don't match
788 if (devicePixelRatio !== backingStoreRatio) {
789
790 var oldWidth = opt_width || canvas.width;
791 var oldHeight = opt_height || canvas.height;
792
793 canvas.width = oldWidth * ratio;
794 canvas.height = oldHeight * ratio;
795
796 canvas.style.width = oldWidth + 'px';
797 canvas.style.height = oldHeight + 'px';
798
799 // Scale the context to counter the fact that we've manually scaled
800 // our canvas element.
801 context.scale(ratio, ratio);
802 return true;
803 }
804 return false;
805 };
806
807
808 /**
809 * Get random number.
810 * @param {number} min
811 * @param {number} max
812 * @param {number}
813 */
814 Runner.getRandomNum = function(min, max) {
815 return Math.floor(Math.random() * (max - min + 1)) + min;
816 };
817
818
819 /**
820 * Vibrate on mobile devices.
821 * @param {number} duration Duration of the vibration in milliseconds.
822 */
823 Runner.vibrate = function(duration) {
824 if (ISMOBILE) {
825 window.navigator['vibrate'](duration);
826 }
827 };
828
829
830 /**
831 * Create canvas element.
832 * @param {HTMLElement} container Element to append canvas to.
833 * @param {number} width
834 * @param {number} height
835 * @param {string} opt_classname
836 * @return {HTMLCanvasElement}
837 */
838 Runner.createCanvas = function(container, width, height, opt_classname) {
839 var canvas = document.createElement('canvas');
840 canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' +
841 opt_classname : Runner.classes.CANVAS;
842 canvas.width = width;
843 canvas.height = height;
844 container.appendChild(canvas);
845
846 return canvas;
847 };
848
849
850 /**
851 * Decodes the base 64 audio to ArrayBuffer used by Web Audio.
852 * @param {string} base64String
853 */
854 Runner.decodeBase64ToArrayBuffer = function(base64String) {
855 var len = (base64String.length / 4) * 3;
856 var str = atob(base64String);
857 var arrayBuffer = new ArrayBuffer(len);
858 var bytes = new Uint8Array(arrayBuffer);
859
860 for (var i = 0; i < len; i++) {
861 bytes[i] = str.charCodeAt(i);
862 }
863 return bytes.buffer;
864 };
865
866
867 //******************************************************************************
868
869
870 /**
871 * Game over panel.
872 * @param {!HTMLCanvasElement} canvas
873 * @param {!HTMLImage} textSprite
874 * @param {!HTMLImage} restartImg
875 * @param {!Object} dimensions Canvas dimensions.
876 * @constructor
877 */
878 Runner.GameOverPanel = function(canvas, textSprite, restartImg, dimensions) {
arv (Not doing code reviews) 2014/09/22 15:03:14 I still do not understand why these all are proper
edwardjung 2014/09/22 16:54:25 Sorry, my laziness.
879 this.canvas = canvas;
880 this.canvasCtx = canvas.getContext('2d');
881 this.canvasDimensions = dimensions;
882 this.textSprite = textSprite;
883 this.restartImg = restartImg;
884 this.draw();
885 };
886
887
888 /**
889 * Dimensions used in the panel.
890 * @enum {number}
891 */
892 Runner.GameOverPanel.dimensions = {
893 TEXT_X: 0,
894 TEXT_Y: 13,
895 TEXT_WIDTH: 191,
896 TEXT_HEIGHT: 11,
897 RESTART_WIDTH: 36,
898 RESTART_HEIGHT: 32
899 };
900
901
902 Runner.GameOverPanel.prototype = {
903 /**
904 * Update the panel dimensions.
905 * @param {number} width New canvas width.
906 * @param {number} opt_height Optional new canvas height.
907 */
908 updateDimensions: function(width, opt_height) {
909 this.canvasDimensions.WIDTH = width;
910 if (opt_height) {
911 this.canvasDimensions.HEIGHT = opt_height;
912 }
913 },
914
915 /**
916 * Draw the panel.
917 */
918 draw: function() {
919 var dimensions = Runner.GameOverPanel.dimensions;
920
921 var centerX = this.canvasDimensions.WIDTH / 2;
922
923 // Game over text.
924 var textSourceX = dimensions.TEXT_X;
925 var textSourceY = dimensions.TEXT_Y;
926 var textSourceWidth = dimensions.TEXT_WIDTH;
927 var textSourceHeight = dimensions.TEXT_HEIGHT;
928
929 var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
930 var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
931 var textTargetWidth = dimensions.TEXT_WIDTH;
932 var textTargetHeight = dimensions.TEXT_HEIGHT;
933
934 var restartSourceWidth = dimensions.RESTART_WIDTH;
935 var restartSourceHeight = dimensions.RESTART_HEIGHT;
936 var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2);
937 var restartTargetY = this.canvasDimensions.HEIGHT / 2;
938
939 if (ISHDPI) {
940 textSourceY *= 2;
941 textSourceX *= 2;
942 textSourceWidth *= 2;
943 textSourceHeight *= 2;
944 restartSourceWidth *= 2;
945 restartSourceHeight *= 2;
946 }
947
948 // Game over text from sprite.
949 this.canvasCtx.drawImage(this.textSprite,
950 textSourceX, textSourceY, textSourceWidth, textSourceHeight,
951 textTargetX, textTargetY, textTargetWidth, textTargetHeight);
952
953 // Restart button.
954 this.canvasCtx.drawImage(this.restartImg, 0, 0,
955 restartSourceWidth, restartSourceHeight,
956 restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
957 dimensions.RESTART_HEIGHT);
958 }
959 };
960
961
962 //******************************************************************************
963
964 /**
965 * Collision checker utilities.
966 */
967 Runner.Collision = function() {};
arv (Not doing code reviews) 2014/09/22 15:03:14 No one seems to be calling this.
edwardjung 2014/09/22 16:54:25 Removed
968
969
970 /**
971 * Check for a collision.
972 * @param {!Runner.Obstacle} obstacle
973 * @param {!Trex} tRex T-rex object.
974 * @param {HTMLCanvasContext} opt_canvasCtx Optional canvas context for drawing
975 * collision boxes.
976 * @return {Array.<Runner.CollisionBox>}
977 */
978 Runner.Collision.check = function(obstacle, tRex, opt_canvasCtx) {
arv (Not doing code reviews) 2014/09/22 15:03:14 Why is this a property of Collision? Maybe change
edwardjung 2014/09/22 16:54:25 Done.
979 var obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos;
980
981 // Adjustments are made to the bounding box as there is a 1 pixel white
982 // border around the t-rex and obstacles.
983 var tRexBox = new Runner.CollisionBox(
984 tRex.xPos + 1,
985 tRex.yPos + 1,
986 tRex.config.WIDTH - 2,
987 tRex.config.HEIGHT - 2);
988
989 var obstacleBox = new Runner.CollisionBox(
990 obstacle.xPos + 1,
991 obstacle.yPos + 1,
992 obstacle.typeConfig.width * obstacle.size - 2,
993 obstacle.typeConfig.height - 2);
994
995 // Debug outer box
996 if (opt_canvasCtx) {
997 Runner.Collision.drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
998 }
999
1000 // Simple outer bounds check.
1001 if (Runner.Collision.boxCompare(tRexBox, obstacleBox)) {
1002 var collisionBoxes = obstacle.collisionBoxes;
1003 var tRexCollisionBoxes = Trex.collisionBoxes;
1004
1005 // Detailed axis aligned box check.
1006 for (var t = 0; t < tRexCollisionBoxes.length; t++) {
1007 for (var i = 0; i < collisionBoxes.length; i++) {
1008 // Adjust the box to actual positions.
1009 var adjTrexBox =
1010 Runner.Collision.createAdjustedCollisionBox(tRexCollisionBoxes[t],
1011 tRexBox);
1012 var adjObstacleBox =
1013 Runner.Collision.createAdjustedCollisionBox(collisionBoxes[i],
1014 obstacleBox);
1015
1016 var crashed = Runner.Collision.boxCompare(adjTrexBox, adjObstacleBox);
1017
1018 // Draw boxes for debug.
1019 if (opt_canvasCtx) {
1020 Runner.Collision.drawCollisionBoxes(opt_canvasCtx,
1021 adjTrexBox, adjObstacleBox);
1022 }
1023
1024 if (crashed) {
1025 return [adjTrexBox, adjObstacleBox];
1026 }
1027 }
1028 }
1029 }
1030 return false;
1031 };
1032
1033
1034 /**
1035 * Adjust the collision box.
1036 * @param {!Runner.CollisionBox} box The original box.
1037 * @param {!Runner.CollisionBox} adjustment Adjustment box.
1038 * @return {Runner.CollisionBox} The adjusted collision box object.
1039 */
1040 Runner.Collision.createAdjustedCollisionBox = function(box, adjustment) {
1041 return new Runner.CollisionBox(
1042 box.x + adjustment.x,
1043 box.y + adjustment.y,
1044 box.width,
1045 box.height);
1046 };
1047
1048
1049 /**
1050 * Draw the collision boxes for debug.
1051 */
1052 Runner.Collision.drawCollisionBoxes =
1053 function(canvasCtx, tRexBox, obstacleBox) {
1054 canvasCtx.save();
1055 canvasCtx.strokeStyle = '#f00';
1056 canvasCtx.strokeRect(tRexBox.x, tRexBox.y,
1057 tRexBox.width, tRexBox.height);
1058
1059 canvasCtx.strokeStyle = '#0f0';
1060 canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,
1061 obstacleBox.width, obstacleBox.height);
1062 canvasCtx.restore();
1063 };
1064
1065
1066 /**
1067 * Compare two collision boxes for a collision.
1068 * @param {Runner.CollisionBox} tRexBox
1069 * @param {Runner.CollisionBox} obstacleBox
1070 * @return {boolean} Whether the boxes intersected.
1071 */
1072 Runner.Collision.boxCompare = function(tRexBox, obstacleBox) {
1073 var crashed = false;
1074 var tRexBoxX = tRexBox.x;
1075 var tRexBoxY = tRexBox.y;
1076
1077 var obstacleBoxX = obstacleBox.x;
1078 var obstacleBoxY = obstacleBox.y;
1079
1080 // Axis-Aligned Bounding Box method.
1081 if (tRexBox.x < obstacleBoxX + obstacleBox.width &&
1082 tRexBox.x + tRexBox.width > obstacleBoxX &&
1083 tRexBox.y < obstacleBox.y + obstacleBox.height &&
1084 tRexBox.height + tRexBox.y > obstacleBox.y) {
1085 crashed = true;
1086 }
1087
1088 return crashed;
1089 };
1090
1091
1092 //******************************************************************************
1093
1094 /**
1095 * Collision box object.
1096 * @param {number} x X position.
1097 * @param {number} y Y Position.
1098 * @param {number} w Width.
1099 * @param {number} h Height.
1100 */
1101 Runner.CollisionBox = function(x, y, w, h) {
1102 this.x = x;
1103 this.y = y;
1104 this.width = w;
1105 this.height = h;
1106 };
1107
1108
1109 //******************************************************************************
1110
1111 /**
1112 * Obstacle.
1113 * @param {HTMLCanvasCtx} canvasCtx
1114 * @param {Runner.Obstacle.type} type
1115 * @param {image} obstacleImg Image sprite.
1116 * @param {Object} dimensions
1117 * @param {number} gapCoefficient Mutipler in determining the gap.
1118 * @param {number} speed
1119 */
1120 Runner.Obstacle = function(canvasCtx, type, obstacleImg, dimensions,
1121 gapCoefficient, speed) {
1122
1123 this.canvasCtx = canvasCtx;
1124 this.image = obstacleImg;
1125 this.typeConfig = type;
1126 this.gapCoefficient = gapCoefficient;
1127 this.size = Runner.getRandomNum(1, Runner.Obstacle.MAX_OBSTACLE_LENGTH);
1128 this.dimensions = dimensions;
1129 this.remove = false;
1130 this.xPos = 0;
1131 this.yPos = this.typeConfig.yPos;
1132 this.width = 0;
1133 this.collisionBoxes = [];
1134 this.gap = 0;
1135
1136 this.init(speed);
1137 };
1138
1139 /**
1140 * Coefficient for calculating the maximum gap.
1141 * @const
1142 */
1143 Runner.Obstacle.MAX_GAP_COEFFICIENT = 1.5;
1144
1145 /**
1146 * Maximum obstacle grouping count.
1147 * @const
1148 */
1149 Runner.Obstacle.MAX_OBSTACLE_LENGTH = 3,
1150
1151
1152 Runner.Obstacle.prototype = {
1153 /**
1154 * Initialise the DOM for the obstacle.
1155 * @param {number} speed
1156 */
1157 init: function(speed) {
1158 this.cloneCollisionBoxes();
1159
1160 // Only allow sizing if we're at the right speed.
1161 if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
1162 this.size = 1;
1163 }
1164
1165 this.width = this.typeConfig.width * this.size;
1166 this.xPos = this.dimensions.WIDTH - this.width;
1167
1168 this.draw();
1169
1170 // Make collision box adjustments,
1171 // Central box is adjusted to the size as one box.
1172 // ____ ______ ________
1173 // _| |-| _| |-| _| |-|
1174 // | |<->| | | |<--->| | | |<----->| |
1175 // | | 1 | | | | 2 | | | | 3 | |
1176 // |_|___|_| |_|_____|_| |_|_______|_|
1177 //
1178 if (this.size > 1) {
1179 this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
1180 this.collisionBoxes[2].width;
1181 this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
1182 }
1183
1184 this.gap = this.getGap(this.gapCoefficient, speed);
1185 },
1186
1187 /**
1188 * Draw and crop based on size.
1189 */
1190 draw: function() {
1191 var sourceWidth = this.typeConfig.width;
1192 var sourceHeight = this.typeConfig.height;
1193
1194 if (ISHDPI) {
1195 sourceWidth = sourceWidth * 2;
1196 sourceHeight = sourceHeight * 2;
1197 }
1198
1199 // Sprite
1200 var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1));
1201 this.canvasCtx.drawImage(this.image,
1202 sourceX, 0,
arv (Not doing code reviews) 2014/09/22 15:03:14 wrong indentation
edwardjung 2014/09/22 16:54:25 Done.
1203 sourceWidth * this.size, sourceHeight,
1204 this.xPos, this.yPos,
1205 this.typeConfig.width * this.size, this.typeConfig.height);
1206 },
1207
1208 /**
1209 * Obstacle frame update.
1210 * @param {number} deltaTime
1211 * @param {number} speed
1212 */
1213 update: function(deltaTime, speed) {
1214 if (!this.remove) {
1215 this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);
1216 this.draw();
1217
1218 if (!this.isVisible()) {
1219 this.remove = true;
1220 }
1221 }
1222 },
1223
1224 /**
1225 * Calculate a random gap size.
1226 * - Minimum gap gets wider as speed increses
1227 * @param {number} gapCoefficient
1228 * @param {number} speed
1229 * @return {number} The gap size.
1230 */
1231 getGap: function(gapCoefficient, speed) {
1232 var minGap = Math.round(this.width * speed +
1233 this.typeConfig.minGap * gapCoefficient);
1234 var maxGap = Math.round(minGap * Runner.Obstacle.MAX_GAP_COEFFICIENT);
1235 return Runner.getRandomNum(minGap, maxGap);
1236 },
1237
1238 /**
1239 * Check if obstacle is visible.
1240 * @return {boolean} Whether the obstacle is in the game area.
1241 */
1242 isVisible: function() {
1243 return this.xPos + this.width > 0;
1244 },
1245
1246 /**
1247 * Make a copy of the collision boxes, since these will change based on
1248 * obstacle type and size.
1249 */
1250 cloneCollisionBoxes: function() {
1251 var collisionBoxes = this.typeConfig.collisionBoxes;
1252
1253 for (var i = collisionBoxes.length - 1; i >= 0; i--) {
1254 this.collisionBoxes[i] = new Runner.CollisionBox(collisionBoxes[i].x,
1255 collisionBoxes[i].y, collisionBoxes[i].width,
1256 collisionBoxes[i].height);
1257 }
1258 }
1259 };
1260
1261
1262 /**
1263 * Obstacle definitions.
1264 * minGap: minimum pixel space betweeen obstacles.
1265 * multipleSpeed: Speed at which multiples are allowed.
1266 */
1267 Runner.Obstacle.types = [
1268 {
1269 type: 'CACTUS_SMALL',
1270 className: ' cactus cactus-small ',
1271 width: 17,
1272 height: 35,
1273 yPos: 105,
1274 multipleSpeed: 3,
1275 minGap: 120,
1276 collisionBoxes: [
1277 new Runner.CollisionBox(0, 7, 5, 27),
arv (Not doing code reviews) 2014/09/22 15:03:15 wrong indentation
edwardjung 2014/09/22 16:54:25 Done.
1278 new Runner.CollisionBox(4, 0, 6, 34),
1279 new Runner.CollisionBox(10, 4, 7, 14)
1280 ]
1281 },
1282 {
1283 type: 'CACTUS_LARGE',
1284 className: ' cactus cactus-large ',
1285 width: 25,
1286 height: 50,
1287 yPos: 90,
1288 multipleSpeed: 6,
1289 minGap: 120,
1290 collisionBoxes: [
arv (Not doing code reviews) 2014/09/22 15:03:14 wrong indentation
edwardjung 2014/09/22 16:54:25 Done.
1291 new Runner.CollisionBox(0, 12, 7, 38),
1292 new Runner.CollisionBox(8, 0, 7, 49),
1293 new Runner.CollisionBox(13, 10, 10, 38)
1294 ]
1295 }
1296 ];
1297
1298
1299 //******************************************************************************
1300 /**
1301 * T-rex game character.
1302 * @param {HTMLCanvas} canvas
1303 * @param {HTMLImage} image Character image.
1304 * @constructor
1305 */
1306 function Trex(canvas, image) {
1307 this.canvas = canvas;
1308 this.canvasCtx = canvas.getContext('2d');
1309 this.image = image;
1310 this.xPos = 0;
1311 this.yPos = 0;
1312 // Position when on the ground.
1313 this.groundYPos = 0;
1314 this.currentFrame = 0;
1315 this.currentAnimFrames = [];
1316 this.blinkDelay = 0;
1317 this.animStartTime = 0;
1318 this.timer = 0;
1319 this.msPerFrame = 1000 / FPS;
1320 this.config = Trex.config;
1321 // Current status.
1322 this.status = Trex.status.WAITING;
1323
1324 this.jumping = false;
1325 this.jumpVelocity = 0;
1326 this.reachedMinHeight = false;
1327 this.speedDrop = false;
1328 this.jumpCount = 0;
1329 this.jumpspotX = 0;
1330
1331 this.init();
1332 };
1333
1334
1335 /**
1336 * T-rex player config.
1337 * @enum {number}
1338 */
1339 Trex.config = {
1340 DROP_VELOCITY: -5,
1341 GRAVITY: 0.6,
1342 HEIGHT: 47,
1343 INIITAL_JUMP_VELOCITY: -10,
1344 INTRO_DURATION: 1500,
1345 MAX_JUMP_HEIGHT: 30,
1346 MIN_JUMP_HEIGHT: 30,
1347 SPEED_DROP_COEFFICIENT: 3,
1348 SPRITE_WIDTH: 262,
1349 START_X_POS: 50,
1350 WIDTH: 44
1351 };
1352
1353
1354 /**
1355 * Used in collision detection.
1356 * @type {Array.<Runner.CollisionBox>}
1357 */
1358 Trex.collisionBoxes = [
1359 new Runner.CollisionBox(1, -1, 30, 26),
1360 new Runner.CollisionBox(32, 0, 8, 16),
1361 new Runner.CollisionBox(10, 35, 14, 8),
1362 new Runner.CollisionBox(1, 24, 29, 5),
1363 new Runner.CollisionBox(5, 30, 21, 4),
1364 new Runner.CollisionBox(9, 34, 15, 4)
1365 ];
1366
1367
1368 /**
1369 * Animation states.
1370 * @enum {string}
1371 */
1372 Trex.status = {
1373 CRASHED: 'CRASHED',
1374 JUMPING: 'JUMPING',
1375 RUNNING: 'RUNNING',
1376 WAITING: 'WAITING'
1377 };
1378
1379 /**
1380 * Blinking coefficient.
1381 * @const
1382 */
1383 Trex.BLINK_TIMING = 7000;
1384
1385
1386 /**
1387 * Animation config for different states.
1388 * @enum {object}
1389 */
1390 Trex.animFrames = {
1391 WAITING: {
1392 frames: [44, 0],
arv (Not doing code reviews) 2014/09/22 15:03:14 wrong indentation
edwardjung 2014/09/22 16:54:25 Done.
1393 msPerFrame: 1000 / 3
1394 },
1395 RUNNING: {
1396 frames: [88, 132],
1397 msPerFrame: 1000 / 12
1398 },
1399 CRASHED: {
1400 frames: [220],
1401 msPerFrame: 1000 / 60
1402 },
1403 JUMPING: {
1404 frames: [0],
1405 msPerFrame: 1000 / 60
1406 }
1407 };
1408
1409
1410 Trex.prototype = {
1411 /**
1412 * T-rex player initaliser.
1413 * Sets the t-rex to blink at random intervals.
1414 */
1415 init: function() {
1416 this.blinkDelay = this.setBlinkDelay();
1417 this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
1418 Runner.config.BOTTOM_PAD;
1419 this.yPos = this.groundYPos;
1420 this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
1421
1422 this.draw(0, 0);
1423 this.update(0, Trex.status.WAITING);
1424 },
1425
1426 /**
1427 * Setter for the jump velocity.
1428 * The approriate drop velocity is also set.
1429 */
1430 setJumpVelocity: function(setting) {
1431 this.config.INIITAL_JUMP_VELOCITY = -setting;
1432 this.config.DROP_VELOCITY = -setting / 2;
1433 },
1434
1435 /**
1436 * Set the animation status.
1437 * @param {!number} deltaTime
1438 * @param {Trex.status} status Optional status to switch to.
1439 */
1440 update: function(deltaTime, opt_status) {
1441 this.timer += deltaTime;
1442
1443 // Update the status.
1444 if (opt_status) {
1445 this.status = opt_status;
1446 this.currentFrame = 0;
1447 this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
1448 this.currentAnimFrames = Trex.animFrames[opt_status].frames;
1449
1450 if (opt_status == Trex.status.WAITING) {
1451 this.animStartTime = performance.now();
1452 this.setBlinkDelay();
1453 }
1454 }
1455
1456 // Game intro animation, T-rex moves in from the left.
1457 if (this.playingIntro && this.xPos < this.config.START_X_POS) {
1458 this.xPos += Math.round((this.config.START_X_POS /
1459 this.config.INTRO_DURATION) * deltaTime);
1460 }
1461
1462 if (this.status == Trex.status.WAITING) {
1463 this.blink(performance.now());
1464 } else {
1465 this.draw(this.currentAnimFrames[this.currentFrame], 0);
1466 }
1467
1468 // Update the frame position.
1469 if (this.timer >= this.msPerFrame) {
1470 this.currentFrame = this.currentFrame ==
1471 this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
1472 this.timer = 0;
1473 }
1474 },
1475
1476 /**
1477 * Draw the t-rex to a particular position.
1478 * @param {number} x
1479 * @param {number} y
1480 */
1481 draw: function(x, y) {
1482 var sourceX = x;
1483 var sourceY = y;
1484 var sourceWidth = this.config.WIDTH;
1485 var sourceHeight = this.config.HEIGHT;
1486
1487 if (ISHDPI) {
1488 sourceX *= 2;
1489 sourceY *= 2;
1490 sourceWidth *= 2;
1491 sourceHeight *= 2;
1492 }
1493
1494 this.canvasCtx.drawImage(this.image, sourceX, sourceY,
1495 sourceWidth, sourceHeight,
1496 this.xPos, this.yPos,
1497 this.config.WIDTH, this.config.HEIGHT);
1498 },
1499
1500 /**
1501 * Sets a random time for the blink to happen.
1502 */
1503 setBlinkDelay: function() {
1504 this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
1505 },
1506
1507 /**
1508 * Make t-rex blink at random intervals.
1509 * @param {number} time Current time in milliseconds.
1510 */
1511 blink: function(time) {
1512 var deltaTime = time - this.animStartTime;
1513
1514 if (deltaTime >= this.blinkDelay) {
1515 this.draw(this.currentAnimFrames[this.currentFrame], 0);
1516
1517 if (this.currentFrame == 1) {
1518 // Set new random delay to blink.
1519 this.setBlinkDelay();
1520 this.animStartTime = time;
1521 }
1522 }
1523 },
1524
1525 /**
1526 * Initialise a jump.
1527 */
1528 startJump: function() {
1529 if (!this.jumping) {
1530 this.update(0, Trex.status.JUMPING);
1531 this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY;
1532 this.jumping = true;
1533 this.reachedMinHeight = false;
1534 this.speedDrop = false;
1535 }
1536 },
1537
1538 /**
1539 * Jump is complete, falling down.
1540 */
1541 endJump: function() {
1542 if (this.reachedMinHeight &&
1543 this.jumpVelocity < this.config.DROP_VELOCITY) {
1544 this.jumpVelocity = this.config.DROP_VELOCITY;
1545 }
1546 },
1547
1548 /**
1549 * Update frame for a jump.
1550 * @param {number} deltaTime
1551 */
1552 updateJump: function(deltaTime) {
1553 var msPerFrame = Trex.animFrames[this.status].msPerFrame;
1554 var framesElapsed = deltaTime / msPerFrame;
1555
1556 // Speed drop makes Trex fall faster.
1557 if (this.speedDrop) {
1558 this.yPos += Math.round(this.jumpVelocity *
1559 this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
1560 } else {
1561 this.yPos += Math.round(this.jumpVelocity * framesElapsed);
1562 }
1563
1564 this.jumpVelocity += this.config.GRAVITY * framesElapsed;
1565
1566 // Minimum height has been reached.
1567 if (this.yPos < this.minJumpHeight || this.speedDrop) {
1568 this.reachedMinHeight = true;
1569 }
1570
1571 // Reached max height
1572 if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
1573 this.endJump();
1574 }
1575
1576 // Back down at ground level. Jump completed.
1577 if (this.yPos > this.groundYPos) {
1578 this.reset();
1579 this.jumpCount++;
1580 }
1581
1582 this.update(deltaTime);
1583 },
1584
1585 /**
1586 * Set the speed drop. Immediately cancels the current jump.
1587 */
1588 setSpeedDrop: function() {
1589 this.speedDrop = true;
1590 this.jumpVelocity = 1;
1591 },
1592
1593 /**
1594 * Reset the t-rex to running at start of game.
1595 */
1596 reset: function() {
1597 this.yPos = this.groundYPos;
1598 this.jumpVelocity = 0;
1599 this.jumping = false;
1600 this.update(0, Trex.status.RUNNING);
1601 this.midair = false;
1602 this.speedDrop = false;
1603 this.jumpCount = 0;
1604 }
1605 };
1606
1607
1608 //******************************************************************************
1609
1610 /**
1611 * Handles displaying the distance meter.
1612 * @param {!HTMLCanvasElement} canvas
1613 * @param {!HTMLImage} spriteSheet Image sprite.
1614 * @param {number} canvasWidth
1615 * @constructor
1616 */
1617 function DistanceMeter(canvas, spriteSheet, canvasWidth) {
1618 this.canvas = canvas;
1619 this.canvasCtx = canvas.getContext('2d');
1620 this.image = spriteSheet;
1621 this.x = 0;
1622 this.y = 5;
1623
1624 this.currentDistance = 0;
1625 this.maxScore = 0;
1626 this.highScore = 0;
1627 this.container = null;
1628
1629 this.digits = [];
1630 this.acheivement = false;
1631 this.defaultString = '';
1632 this.flashTimer = 0;
1633 this.flashIterations = 0;
1634
1635 this.config = DistanceMeter.config;
1636 this.init(canvasWidth);
1637 };
1638
1639
1640 /**
1641 * @enum {number}
1642 */
1643 DistanceMeter.dimensions = {
1644 WIDTH: 10,
1645 HEIGHT: 13,
1646 DEST_WIDTH: 11
1647 };
1648
1649
1650 /**
1651 * Y positioning of the digits in the sprite sheet.
1652 * X position is always 0.
1653 * @type {array.<number>}
1654 */
1655 DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];
1656
1657
1658 /**
1659 * Distance meter config.
1660 * @enum {number}
1661 */
1662 DistanceMeter.config = {
1663 // Number of digits.
1664 MAX_DISTANCE_UNITS: 5,
1665
1666 // Distance that causes achievement animation.
1667 ACHIEVEMENT_DISTANCE: 100,
1668
1669 // Used for conversion from pixel distance to a scaled unit.
1670 COEFFICIENT: 0.025,
1671
1672 // Flash duration in milliseconds.
1673 FLASH_DURATION: 1000 / 4,
1674
1675 // Flash iterations for achievement animation.
1676 FLASH_ITERATIONS: 3
1677 };
1678
1679
1680 DistanceMeter.prototype = {
1681 /**
1682 * Initialise the distance meter to '00000'.
1683 * @param {number} width Canvas width in px.
1684 */
1685 init: function(width) {
1686 var maxDistanceStr = '';
1687
1688 this.calcXPos(width);
1689 this.maxScore = this.config.MAX_DISTANCE_UNITS;
1690 for (var i = 0; i < this.config.MAX_DISTANCE_UNITS; i++) {
1691 this.draw(i, 0);
1692 this.defaultString += '0';
1693 maxDistanceStr += '9';
1694 }
1695
1696 this.maxScore = parseInt(maxDistanceStr);
1697 },
1698
1699 /**
1700 * Calculate the xPos in the canvas.
1701 * @param {number} canvasWidth
1702 */
1703 calcXPos: function(canvasWidth) {
1704 this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
1705 (this.config.MAX_DISTANCE_UNITS + 1));
1706 },
1707
1708 /**
1709 * Draw a digit to canvas.
1710 * @param {number} digitPos Position of the digit.
1711 * @param {number} value Digit value 0-9.
1712 * @param {boolean} opt_highScore Whether drawing the high score.
1713 */
1714 draw: function(digitPos, value, opt_highScore) {
1715 var sourceWidth = DistanceMeter.dimensions.WIDTH;
1716 var sourceHeight = DistanceMeter.dimensions.HEIGHT;
1717 var sourceX = DistanceMeter.dimensions.WIDTH * value;
1718
1719 var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
1720 var targetY = this.y;
1721 var targetWidth = DistanceMeter.dimensions.WIDTH;
1722 var targetHeight = DistanceMeter.dimensions.HEIGHT;
1723
1724 // For high DPI we 2x source values.
1725 if (ISHDPI) {
1726 sourceWidth *= 2;
1727 sourceHeight *= 2;
1728 sourceX *= 2;
1729 }
1730
1731 this.canvasCtx.save();
1732
1733 if (opt_highScore) {
1734 // Left of the current score.
1735 var highScoreX = this.x - (this.config.MAX_DISTANCE_UNITS * 2) *
1736 DistanceMeter.dimensions.WIDTH;
1737 this.canvasCtx.translate(highScoreX, this.y);
1738 } else {
1739 this.canvasCtx.translate(this.x, this.y);
1740 }
1741
1742 this.canvasCtx.drawImage(this.image, sourceX, 0,
1743 sourceWidth, sourceHeight,
1744 targetX, targetY,
1745 targetWidth, targetHeight
1746 );
1747
1748 this.canvasCtx.restore();
1749 },
1750
1751 /**
1752 * Covert pixel distance to a 'real' distance.
1753 * @param {number} distance Pixel distance ran.
1754 * @return {number} The 'real' distance ran.
1755 */
1756 getActualDistance: function(distance) {
1757 return distance ?
1758 Math.round(distance * this.config.COEFFICIENT) : 0;
1759 },
1760
1761 /**
1762 * Update the distance meter.
1763 * @param {number} deltaTime
1764 * @param {number} distance
1765 * @return {boolean} Whether the acheivement sound fx should be played.
1766 */
1767 update: function(deltaTime, distance) {
1768 var paint = true;
1769 var playSound = false;
1770
1771 if (!this.acheivement) {
1772 distance = this.getActualDistance(distance);
1773
1774 if (distance > 0) {
1775 // Acheivement unlocked
1776 if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) {
1777 // Flash score and play sound.
1778 this.acheivement = true;
1779 this.flashTimer = 0;
1780 playSound = true;
1781 }
1782
1783 // Create a string representation of the distance with leading 0.
1784 var distanceStr = (this.defaultString +
1785 distance).substr(-this.config.MAX_DISTANCE_UNITS);
1786 this.digits = distanceStr.split('');
1787 } else {
1788 this.digits = this.defaultString.split('');
1789 }
1790 } else {
1791 // Control flashing of the score on reaching acheivement.
1792 if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
1793 this.flashTimer += deltaTime;
1794
1795 if (this.flashTimer < this.config.FLASH_DURATION) {
1796 paint = false;
1797 } else if (this.flashTimer >
1798 this.config.FLASH_DURATION * 2) {
1799 this.flashTimer = 0;
1800 this.flashIterations++;
1801 }
1802 } else {
1803 this.acheivement = false;
1804 this.flashIterations = 0;
1805 this.flashTimer = 0;
1806 }
1807 }
1808
1809 // Draw the digits if not flashing.
1810 if (paint) {
1811 for (var i = this.digits.length - 1; i >= 0; i--) {
1812 this.draw(i, parseInt(this.digits[i]));
1813 }
1814 }
1815
1816 this.drawHighScore();
1817
1818 return playSound;
1819 },
1820
1821 /**
1822 * Draw the high score.
1823 */
1824 drawHighScore: function() {
1825 this.canvasCtx.save();
1826 this.canvasCtx.globalAlpha = .8;
1827 for (var i = this.highScore.length - 1; i >= 0; i--) {
1828 this.draw(i, parseInt(this.highScore[i], 10), true);
1829 }
1830 this.canvasCtx.restore();
1831 },
1832
1833 /**
1834 * Set the highscore as a array string.
1835 * Position of char in the sprite: H - 10, I - 11.
1836 * @param {number} distance Distance ran in pixels.
1837 */
1838 setHighScore: function(distance) {
1839 distance = this.getActualDistance(distance);
1840 var highScoreStr = (this.defaultString +
1841 distance).substr(-this.config.MAX_DISTANCE_UNITS);
1842
1843 this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
1844 },
1845
1846 /**
1847 * Reset the distance meter back to '00000'.
1848 */
1849 reset: function() {
1850 this.update(0);
1851 this.acheivement = false;
1852 }
1853 };
1854
1855
1856 //******************************************************************************
1857
1858 /**
1859 * Cloud background item.
1860 * Similar to an obstacle object but without collision boxes.
1861 * @param {HTMLCanvasElement} canvas Canvas element.
1862 * @param {Image} cloudImg
1863 * @param {number} containerWidth
1864 */
1865 function Cloud(canvas, cloudImg, containerWidth) {
1866 this.canvas = canvas;
1867 this.canvasCtx = this.canvas.getContext('2d');
1868 this.image = cloudImg;
1869 this.containerWidth = containerWidth;
1870 this.xPos = containerWidth;
1871 this.yPos = 0;
1872 this.remove = false;
1873 this.cloudGap = Runner.getRandomNum(Cloud.config.MIN_CLOUD_GAP,
1874 Cloud.config.MAX_CLOUD_GAP);
1875
1876 this.init();
1877 };
1878
1879
1880 /**
1881 * Cloud object config.
1882 * @enum {number}
1883 */
1884 Cloud.config = {
1885 HEIGHT: 13,
1886 MAX_CLOUD_GAP: 400,
1887 MAX_SKY_LEVEL: 30,
1888 MIN_CLOUD_GAP: 100,
1889 MIN_SKY_LEVEL: 71,
1890 WIDTH: 46
1891 };
1892
1893
1894 Cloud.prototype = {
1895 /**
1896 * Initialise the cloud. Sets the Cloud height.
1897 */
1898 init: function() {
1899 this.yPos = Runner.getRandomNum(Cloud.config.MAX_SKY_LEVEL,
1900 Cloud.config.MIN_SKY_LEVEL);
1901 this.draw();
1902 },
1903
1904 /**
1905 * Draw the cloud.
1906 */
1907 draw: function() {
1908 this.canvasCtx.save();
1909 var sourceWidth = Cloud.config.WIDTH;
1910 var sourceHeight = Cloud.config.HEIGHT;
1911
1912 if (ISHDPI) {
1913 sourceWidth = sourceWidth * 2;
1914 sourceHeight = sourceHeight * 2;
1915 }
1916
1917 this.canvasCtx.drawImage(this.image, 0, 0,
1918 sourceWidth, sourceHeight,
1919 this.xPos, this.yPos,
1920 Cloud.config.WIDTH, Cloud.config.HEIGHT);
1921
1922 this.canvasCtx.restore();
1923 },
1924
1925 /**
1926 * Update the cloud position.
1927 * @param {number} speed
1928 */
1929 update: function(speed) {
1930 if (!this.remove) {
1931 this.xPos -= Math.ceil(speed);
1932 this.draw();
1933
1934 // Mark as removeable if no longer in the canvas.
1935 if (!this.isVisible()) {
1936 this.remove = true;
1937 }
1938 }
1939 },
1940
1941 /**
1942 * Check if the cloud is visible on the stage.
1943 * @return {boolean}
1944 */
1945 isVisible: function() {
1946 return this.xPos + Cloud.config.WIDTH > 0;
1947 }
1948 };
1949
1950
1951 //******************************************************************************
1952
1953 /**
1954 * Horizon Line.
1955 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
1956 * @param {HTMLCanvasElement} canvas
1957 * @param {HTMLImage} bgImg Horizon line sprite.
1958 * @constructor
1959 */
1960 function HorizonLine(canvas, bgImg) {
1961 this.image = bgImg;
1962 this.canvas = canvas;
1963 this.canvasCtx = canvas.getContext('2d');
1964 this.sourceDimensions = {};
1965 this.dimensions = HorizonLine.dimensions;
1966 this.sourceXPos = [0, this.dimensions.WIDTH];
1967 this.xPos = [];
1968 this.yPos = 0;
1969 this.bumpThreshold = 0.5;
1970
1971 this.setSourceDimensions();
1972 this.draw();
1973 };
1974
1975
1976 /**
1977 * Horizon line dimensions.
1978 * @enum {number}
1979 */
1980 HorizonLine.dimensions = {
1981 WIDTH: 600,
1982 HEIGHT: 12,
1983 YPOS: 127
1984 };
1985
1986
1987 HorizonLine.prototype = {
1988 /**
1989 * Set the source dimensions of the horizon line.
1990 */
1991 setSourceDimensions: function() {
1992
1993 for (var dimension in HorizonLine.dimensions) {
1994 if (ISHDPI) {
1995 if (dimension != 'YPOS') {
1996 this.sourceDimensions[dimension] =
1997 HorizonLine.dimensions[dimension] * 2;
1998 }
1999 } else {
2000 this.sourceDimensions[dimension] =
2001 HorizonLine.dimensions[dimension];
2002 }
2003 this.dimensions[dimension] = HorizonLine.dimensions[dimension];
2004 }
2005
2006 this.xPos = [0, HorizonLine.dimensions.WIDTH];
2007 this.yPos = HorizonLine.dimensions.YPOS;
2008 },
2009
2010 /**
2011 * Return the crop x position of a type.
2012 */
2013 getRandomType: function() {
2014 return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
2015 },
2016
2017 /**
2018 * Draw the horizon line.
2019 */
2020 draw: function() {
2021 this.canvasCtx.drawImage(this.image, this.sourceXPos[0], 0,
2022 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
2023 this.xPos[0], this.yPos,
2024 this.dimensions.WIDTH, this.dimensions.HEIGHT);
2025
2026 this.canvasCtx.drawImage(this.image, this.sourceXPos[1], 0,
2027 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
2028 this.xPos[1], this.yPos,
2029 this.dimensions.WIDTH, this.dimensions.HEIGHT);
2030 },
2031
2032 /**
2033 * Update the x position of an indivdual piece of the line.
2034 * @param {number} pos Line position.
2035 * @param {number} increment
2036 */
2037 updateXPos: function(pos, increment) {
2038 var line1 = pos;
2039 var line2 = pos == 0 ? 1 : 0;
2040
2041 this.xPos[line1] -= increment;
2042 this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
2043
2044 if (this.xPos[line1] <= -this.dimensions.WIDTH) {
2045 this.xPos[line1] += this.dimensions.WIDTH * 2;
2046 this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
2047 this.sourceXPos[line1] = this.getRandomType();
2048 }
2049 },
2050
2051 /**
2052 * Update the horizon line.
2053 * @param {number} deltaTime
2054 * @param {number} speed
2055 */
2056 update: function(deltaTime, speed) {
2057 var increment = Math.floor(speed * (FPS / 1000) * deltaTime);
2058
2059 if (this.xPos[0] <= 0) {
2060 this.updateXPos(0, increment);
2061 } else {
2062 this.updateXPos(1, increment);
2063 }
2064 this.draw();
2065 },
2066
2067 /**
2068 * Reset horizon to the starting position.
2069 */
2070 reset: function() {
2071 this.xPos[0] = 0;
2072 this.xPos[1] = HorizonLine.dimensions.WIDTH;
2073 }
2074 };
2075
2076
2077 //******************************************************************************
2078
2079 /**
2080 * Horizon background class.
2081 * @param {HTMLCanvasElement} canvas
2082 * @param {Array.<HTMLImageElement>} images
2083 * @param {object} dimensions Canvas dimensions.
2084 * @param {number} gapCoefficient
2085 * @constructor
2086 */
2087 function Horizon(canvas, images, dimensions, gapCoefficient) {
2088 this.canvas = canvas;
2089 this.canvasCtx = this.canvas.getContext('2d');
2090 this.config = Horizon.config;
2091 this.dimensions = dimensions;
2092 this.gapCoefficient = gapCoefficient;
2093 this.obstacles = [];
2094 this.horizonOffsets = [0, 0];
2095 this.cloudFrequency = this.config.CLOUD_FREQUENCY;
2096
2097 // Cloud
2098 this.clouds = [];
2099 this.cloudImg = images.CLOUD;
2100 this.cloudSpeed = this.config.BG_CLOUD_SPEED;
2101
2102 // Horizon
2103 this.horizonImg = images.HORIZON;
2104 this.horizonLine = null;
2105
2106 // Obstacles
2107 this.obstacleImgs = {
2108 CACTUS_SMALL: images.CACTUS_SMALL,
2109 CACTUS_LARGE: images.CACTUS_LARGE
2110 };
2111
2112 this.init();
2113 };
2114
2115
2116 /**
2117 * Horizon config.
2118 * @enum {number}
2119 */
2120 Horizon.config = {
2121 BG_CLOUD_SPEED: 0.2,
2122 BUMPY_THRESHOLD: .3,
2123 CLOUD_FREQUENCY: .5,
2124 HORIZON_HEIGHT: 16,
2125 MAX_CLOUDS: 6
2126 };
2127
2128
2129 Horizon.prototype = {
2130 /**
2131 * Initialise the horizon. Just add the line and a cloud. No obstacles.
2132 */
2133 init: function() {
2134 this.addCloud();
2135 this.horizonLine = new HorizonLine(this.canvas, this.horizonImg);
2136 },
2137
2138 /**
2139 * @param {number} deltaTime
2140 * @param {number} currentSpeed
2141 * @param {boolean} updateObstacles Used as an override to prevent
2142 * the obstacles from being updated / added. This happens in the
2143 * ease in section.
2144 */
2145 update: function(deltaTime, currentSpeed, updateObstacles) {
2146 this.runningTime += deltaTime;
2147 this.horizonLine.update(deltaTime, currentSpeed);
2148 this.updateClouds(deltaTime, currentSpeed);
2149
2150 if (updateObstacles) {
2151 this.updateObstacles(deltaTime, currentSpeed);
2152 }
2153 },
2154
2155 /**
2156 * Update the cloud positions.
2157 * @param {number} deltaTime
2158 * @param {number} currentSpeed
2159 */
2160 updateClouds: function(deltaTime, speed) {
2161 var cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed;
2162 var numClouds = this.clouds.length;
2163
2164 if (numClouds) {
2165 for (var i = numClouds - 1; i >= 0; i--) {
2166 this.clouds[i].update(cloudSpeed);
2167 }
2168
2169 var lastCloud = this.clouds[numClouds - 1];
2170
2171 // Check for adding a new cloud.
2172 if (numClouds < this.config.MAX_CLOUDS &&
2173 (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap &&
2174 this.cloudFrequency > Math.random()) {
2175 this.addCloud();
2176 }
2177
2178 // Remove expired clouds.
2179 this.clouds = this.clouds.filter(function(obj) {
2180 return !obj.remove;
2181 });
2182 }
2183 },
2184
2185 /**
2186 * Update the obstacle positions.
2187 * @param {number} deltaTime
2188 * @param {number} currentSpeed
2189 */
2190 updateObstacles: function(deltaTime, currentSpeed) {
2191 // Obstacles, move to Horizon layer.
2192 var updatedObstacles = this.obstacles.slice(0);
2193
2194 for (var i = 0; i < this.obstacles.length; i++) {
2195 var obstacle = this.obstacles[i];
2196 obstacle.update(deltaTime, currentSpeed);
2197
2198 // Clean up existing obstacles.
2199 if (obstacle.remove) {
2200 updatedObstacles.shift();
2201 }
2202 }
2203 this.obstacles = updatedObstacles;
2204
2205 if (this.obstacles.length > 0) {
2206 var lastObstacle = this.obstacles[this.obstacles.length - 1];
2207
2208 if (lastObstacle && !lastObstacle.followingObstacleCreated &&
2209 lastObstacle.isVisible() &&
2210 (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
2211 this.dimensions.WIDTH) {
2212 this.addNewObstacle(currentSpeed);
2213 lastObstacle.followingObstacleCreated = true;
2214 }
2215 } else {
2216 // Create new obstacles.
2217 this.addNewObstacle(currentSpeed);
2218 }
2219 },
2220
2221 /**
2222 * Add a new obstacle.
2223 * @param {number} currentSpeed
2224 */
2225 addNewObstacle: function(currentSpeed) {
2226 var obstacleTypeIndex =
2227 Runner.getRandomNum(0, Runner.Obstacle.types.length - 1);
2228 var obstacleType = Runner.Obstacle.types[obstacleTypeIndex];
2229 var obstacleImg = this.obstacleImgs[obstacleType.type];
2230
2231 this.obstacles.push(new Runner.Obstacle(this.canvasCtx, obstacleType,
2232 obstacleImg, this.dimensions, this.gapCoefficient, currentSpeed));
2233 },
2234
2235 /**
2236 * Reset the horizon layer.
2237 * Remove existing obstacles and reposition the horizon line.
2238 */
2239 reset: function() {
2240 this.obstacles = [];
2241 this.horizonLine.reset();
2242 },
2243
2244 /**
2245 * Update the canvas width and scaling.
2246 * @param {number} width Canvas width.
2247 * @param {number} height Canvas height.
2248 */
2249 resize: function(width, height) {
2250 this.canvas.width = width;
2251 this.canvas.height = height;
2252 },
2253
2254 /**
2255 * Add a new cloud to the horizon.
2256 */
2257 addCloud: function() {
2258 this.clouds.push(new Cloud(this.canvas, this.cloudImg,
2259 this.dimensions.WIDTH));
2260 }
2261 };
2262 })();
OLDNEW
« no previous file with comments | « chrome/renderer/resources/neterror.js ('k') | chrome/renderer/resources/sounds/button-press.mp3 » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698