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

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

Issue 1051433002: Improvements to the offline intersitial and easter egg (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 8 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
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 (function() { 4 (function() {
5 'use strict'; 5 'use strict';
6 /** 6 /**
7 * T-Rex runner. 7 * T-Rex runner.
8 * @param {string} outerContainerId Outer containing element id. 8 * @param {string} outerContainerId Outer containing element id.
9 * @param {object} opt_config 9 * @param {object} opt_config
10 * @constructor 10 * @constructor
11 * @export 11 * @export
12 */ 12 */
13 function Runner(outerContainerId, opt_config) { 13 function Runner(outerContainerId, opt_config) {
14 // Singleton 14 // Singleton
15 if (Runner.instance_) { 15 if (Runner.instance_) {
16 return Runner.instance_; 16 return Runner.instance_;
17 } 17 }
18 Runner.instance_ = this; 18 Runner.instance_ = this;
19 19
20 this.outerContainerEl = document.querySelector(outerContainerId); 20 this.outerContainerEl = document.querySelector(outerContainerId);
21 this.containerEl = null; 21 this.containerEl = null;
22 this.snackbarEl = null;
22 this.detailsButton = this.outerContainerEl.querySelector('#details-button'); 23 this.detailsButton = this.outerContainerEl.querySelector('#details-button');
23 24
24 this.config = opt_config || Runner.config; 25 this.config = opt_config || Runner.config;
25 26
26 this.dimensions = Runner.defaultDimensions; 27 this.dimensions = Runner.defaultDimensions;
27 28
28 this.canvas = null; 29 this.canvas = null;
29 this.canvasCtx = null; 30 this.canvasCtx = null;
30 31
31 this.tRex = null; 32 this.tRex = null;
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
80 /** 81 /**
81 * Frames per second. 82 * Frames per second.
82 * @const 83 * @const
83 */ 84 */
84 var FPS = 60; 85 var FPS = 60;
85 86
86 /** @const */ 87 /** @const */
87 var IS_HIDPI = window.devicePixelRatio > 1; 88 var IS_HIDPI = window.devicePixelRatio > 1;
88 89
89 /** @const */ 90 /** @const */
90 var IS_IOS = 91 var IS_IOS = window.navigator.userAgent.indexOf('CriOS') > -1 ||
91 window.navigator.userAgent.indexOf('UIWebViewForStaticFileContent') > -1; 92 window.navigator.userAgent == 'UIWebViewForStaticFileContent';
92 93
93 /** @const */ 94 /** @const */
94 var IS_MOBILE = window.navigator.userAgent.indexOf('Mobi') > -1 || IS_IOS; 95 var IS_MOBILE = window.navigator.userAgent.indexOf('Mobi') > -1 || IS_IOS;
95 96
96 /** @const */ 97 /** @const */
97 var IS_TOUCH_ENABLED = 'ontouchstart' in window; 98 var IS_TOUCH_ENABLED = 'ontouchstart' in window;
98 99
99 /** 100 /**
100 * Default game configuration. 101 * Default game configuration.
101 * @enum {number} 102 * @enum {number}
102 */ 103 */
103 Runner.config = { 104 Runner.config = {
104 ACCELERATION: 0.001, 105 ACCELERATION: 0.001,
105 BG_CLOUD_SPEED: 0.2, 106 BG_CLOUD_SPEED: 0.2,
106 BOTTOM_PAD: 10, 107 BOTTOM_PAD: 10,
107 CLEAR_TIME: 3000, 108 CLEAR_TIME: 3000,
108 CLOUD_FREQUENCY: 0.5, 109 CLOUD_FREQUENCY: 0.5,
109 GAMEOVER_CLEAR_TIME: 750, 110 GAMEOVER_CLEAR_TIME: 750,
110 GAP_COEFFICIENT: 0.6, 111 GAP_COEFFICIENT: 0.6,
111 GRAVITY: 0.6, 112 GRAVITY: 0.6,
112 INITIAL_JUMP_VELOCITY: 12, 113 INITIAL_JUMP_VELOCITY: 12,
113 MAX_CLOUDS: 6, 114 MAX_CLOUDS: 6,
114 MAX_OBSTACLE_LENGTH: 3, 115 MAX_OBSTACLE_LENGTH: 3,
115 MAX_SPEED: 12, 116 MAX_OBSTACLE_DUPLICATION: 2,
117 MAX_SPEED: 13,
116 MIN_JUMP_HEIGHT: 35, 118 MIN_JUMP_HEIGHT: 35,
117 MOBILE_SPEED_COEFFICIENT: 1.2, 119 MOBILE_SPEED_COEFFICIENT: 1.2,
118 RESOURCE_TEMPLATE_ID: 'audio-resources', 120 RESOURCE_TEMPLATE_ID: 'audio-resources',
119 SPEED: 6, 121 SPEED: 6,
120 SPEED_DROP_COEFFICIENT: 3 122 SPEED_DROP_COEFFICIENT: 3
121 }; 123 };
122 124
123 125
124 /** 126 /**
125 * Default dimensions. 127 * Default dimensions.
(...skipping 14 matching lines...) Expand all
140 CONTAINER: 'runner-container', 142 CONTAINER: 'runner-container',
141 CRASHED: 'crashed', 143 CRASHED: 'crashed',
142 ICON: 'icon-offline', 144 ICON: 'icon-offline',
143 SNACKBAR: 'snackbar', 145 SNACKBAR: 'snackbar',
144 SNACKBAR_SHOW: 'snackbar-show', 146 SNACKBAR_SHOW: 'snackbar-show',
145 TOUCH_CONTROLLER: 'controller' 147 TOUCH_CONTROLLER: 'controller'
146 }; 148 };
147 149
148 150
149 /** 151 /**
150 * Image source urls. 152 * Sprite definition layout of the spritesheet.
151 * @enum {array<object>} 153 * @enum {object}
152 */ 154 */
153 Runner.imageSources = { 155 Runner.spriteDefinition = {
154 LDPI: [ 156 LDPI: {
155 {name: 'CACTUS_LARGE', id: '1x-obstacle-large'}, 157 CACTUS_LARGE: {x: 332, y: 2},
156 {name: 'CACTUS_SMALL', id: '1x-obstacle-small'}, 158 CACTUS_SMALL: {x: 228, y: 2},
157 {name: 'CLOUD', id: '1x-cloud'}, 159 CLOUD: {x: 86, y: 2},
158 {name: 'HORIZON', id: '1x-horizon'}, 160 HORIZON: {x: 2, y: 54},
159 {name: 'RESTART', id: '1x-restart'}, 161 PTERODACTYL: {x: 134, y: 2},
160 {name: 'TEXT_SPRITE', id: '1x-text'}, 162 RESTART: {x: 2, y: 2},
161 {name: 'TREX', id: '1x-trex'} 163 TEXT_SPRITE: {x: 484, y: 2},
162 ], 164 TREX: {x: 677, y: 2}
163 HDPI: [ 165 },
164 {name: 'CACTUS_LARGE', id: '2x-obstacle-large'}, 166 HDPI: {
165 {name: 'CACTUS_SMALL', id: '2x-obstacle-small'}, 167 CACTUS_LARGE: {x: 652,y: 2},
166 {name: 'CLOUD', id: '2x-cloud'}, 168 CACTUS_SMALL: {x: 446,y: 2},
167 {name: 'HORIZON', id: '2x-horizon'}, 169 CLOUD: {x: 166,y: 2},
168 {name: 'RESTART', id: '2x-restart'}, 170 HORIZON: {x: 2,y: 104},
169 {name: 'TEXT_SPRITE', id: '2x-text'}, 171 PTERODACTYL: {x: 260,y: 2},
170 {name: 'TREX', id: '2x-trex'} 172 RESTART: {x: 2,y: 2},
171 ] 173 TEXT_SPRITE: {x: 954,y: 2},
174 TREX: {x: 1338,y: 2}
175 }
172 }; 176 };
173 177
174 178
175 /** 179 /**
176 * Sound FX. Reference to the ID of the audio tag on interstitial page. 180 * Sound FX. Reference to the ID of the audio tag on interstitial page.
177 * @enum {string} 181 * @enum {string}
178 */ 182 */
179 Runner.sounds = { 183 Runner.sounds = {
180 BUTTON_PRESS: 'offline-sound-press', 184 BUTTON_PRESS: 'offline-sound-press',
181 HIT: 'offline-sound-hit', 185 HIT: 'offline-sound-hit',
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
261 this.tRex.setJumpVelocity(value); 265 this.tRex.setJumpVelocity(value);
262 break; 266 break;
263 case 'SPEED': 267 case 'SPEED':
264 this.setSpeed(value); 268 this.setSpeed(value);
265 break; 269 break;
266 } 270 }
267 } 271 }
268 }, 272 },
269 273
270 /** 274 /**
271 * Load and cache the image assets from the page. 275 * Cache the approprate image sprite from the page and get the sprite sheet
arv (Not doing code reviews) 2015/03/31 19:10:56 typo
edwardjung 2015/04/01 10:40:46 Done.
276 * definition.
272 */ 277 */
273 loadImages: function() { 278 loadImages: function() {
274 var imageSources = IS_HIDPI ? Runner.imageSources.HDPI : 279 if (IS_HIDPI) {
275 Runner.imageSources.LDPI; 280 Runner.imageSprite = document.getElementById('offline-resources-2x');
281 this.spriteDef = Runner.spriteDefinition.HDPI;
282 } else {
283 Runner.imageSprite = document.getElementById('offline-resources-1x');
284 this.spriteDef = Runner.spriteDefinition.LDPI;
285 }
276 286
277 var numImages = imageSources.length;
278
279 for (var i = numImages - 1; i >= 0; i--) {
280 var imgSource = imageSources[i];
281 this.images[imgSource.name] = document.getElementById(imgSource.id);
282 }
283 this.init(); 287 this.init();
284 }, 288 },
285 289
286 /** 290 /**
287 * Load and decode base 64 encoded sounds. 291 * Load and decode base 64 encoded sounds.
288 */ 292 */
289 loadSounds: function() { 293 loadSounds: function() {
290 if (!IS_IOS) { 294 if (!IS_IOS) {
291 this.audioContext = new AudioContext(); 295 this.audioContext = new AudioContext();
296
292 var resourceTemplate = 297 var resourceTemplate =
293 document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content; 298 document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content;
294 299
295 for (var sound in Runner.sounds) { 300 for (var sound in Runner.sounds) {
296 var soundSrc = 301 var soundSrc =
297 resourceTemplate.getElementById(Runner.sounds[sound]).src; 302 resourceTemplate.getElementById(Runner.sounds[sound]).src;
298 soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1); 303 soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);
299 var buffer = decodeBase64ToArrayBuffer(soundSrc); 304 var buffer = decodeBase64ToArrayBuffer(soundSrc);
300 305
301 // Async, so no guarantee of order in array. 306 // Async, so no guarantee of order in array.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
340 // Player canvas container. 345 // Player canvas container.
341 this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH, 346 this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH,
342 this.dimensions.HEIGHT, Runner.classes.PLAYER); 347 this.dimensions.HEIGHT, Runner.classes.PLAYER);
343 348
344 this.canvasCtx = this.canvas.getContext('2d'); 349 this.canvasCtx = this.canvas.getContext('2d');
345 this.canvasCtx.fillStyle = '#f7f7f7'; 350 this.canvasCtx.fillStyle = '#f7f7f7';
346 this.canvasCtx.fill(); 351 this.canvasCtx.fill();
347 Runner.updateCanvasScaling(this.canvas); 352 Runner.updateCanvasScaling(this.canvas);
348 353
349 // Horizon contains clouds, obstacles and the ground. 354 // Horizon contains clouds, obstacles and the ground.
350 this.horizon = new Horizon(this.canvas, this.images, this.dimensions, 355 this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions,
351 this.config.GAP_COEFFICIENT); 356 this.config.GAP_COEFFICIENT);
352 357
353 // Distance meter 358 // Distance meter
354 this.distanceMeter = new DistanceMeter(this.canvas, 359 this.distanceMeter = new DistanceMeter(this.canvas,
355 this.images.TEXT_SPRITE, this.dimensions.WIDTH); 360 this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH);
356 361
357 // Draw t-rex 362 // Draw t-rex
358 this.tRex = new Trex(this.canvas, this.images.TREX); 363 this.tRex = new Trex(this.canvas, this.spriteDef.TREX);
359 364
360 this.outerContainerEl.appendChild(this.containerEl); 365 this.outerContainerEl.appendChild(this.containerEl);
361 366
362 if (IS_MOBILE) { 367 if (IS_MOBILE) {
363 this.createTouchController(); 368 this.createTouchController();
364 } 369 }
365 370
366 this.startListening(); 371 this.startListening();
367 this.update(); 372 this.update();
368 373
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after
497 this.drawPending = false; 502 this.drawPending = false;
498 503
499 var now = getTimeStamp(); 504 var now = getTimeStamp();
500 var deltaTime = now - (this.time || now); 505 var deltaTime = now - (this.time || now);
501 this.time = now; 506 this.time = now;
502 507
503 if (this.activated) { 508 if (this.activated) {
504 this.clearCanvas(); 509 this.clearCanvas();
505 510
506 if (this.tRex.jumping) { 511 if (this.tRex.jumping) {
507 this.tRex.updateJump(deltaTime, this.config); 512 this.tRex.updateJump(deltaTime);
508 } 513 }
509 514
510 this.runningTime += deltaTime; 515 this.runningTime += deltaTime;
511 var hasObstacles = this.runningTime > this.config.CLEAR_TIME; 516 var hasObstacles = this.runningTime > this.config.CLEAR_TIME;
512 517
513 // First jump triggers the intro. 518 // First jump triggers the intro.
514 if (this.tRex.jumpCount == 1 && !this.playingIntro) { 519 if (this.tRex.jumpCount == 1 && !this.playingIntro) {
515 this.playIntro(); 520 this.playIntro();
516 } 521 }
517 522
(...skipping 12 matching lines...) Expand all
530 if (!collision) { 535 if (!collision) {
531 this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame; 536 this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;
532 537
533 if (this.currentSpeed < this.config.MAX_SPEED) { 538 if (this.currentSpeed < this.config.MAX_SPEED) {
534 this.currentSpeed += this.config.ACCELERATION; 539 this.currentSpeed += this.config.ACCELERATION;
535 } 540 }
536 } else { 541 } else {
537 this.gameOver(); 542 this.gameOver();
538 } 543 }
539 544
540 if (this.distanceMeter.getActualDistance(this.distanceRan) >
541 this.distanceMeter.maxScore) {
542 this.distanceRan = 0;
543 }
544
545 var playAcheivementSound = this.distanceMeter.update(deltaTime, 545 var playAcheivementSound = this.distanceMeter.update(deltaTime,
546 Math.ceil(this.distanceRan)); 546 Math.ceil(this.distanceRan));
547 547
548 if (playAcheivementSound) { 548 if (playAcheivementSound) {
549 this.playSound(this.soundFx.SCORE); 549 this.playSound(this.soundFx.SCORE);
550 } 550 }
551 } 551 }
552 552
553 if (!this.crashed) { 553 if (!this.crashed) {
554 this.tRex.update(deltaTime); 554 this.tRex.update(deltaTime);
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
611 document.removeEventListener(Runner.events.MOUSEDOWN, this); 611 document.removeEventListener(Runner.events.MOUSEDOWN, this);
612 document.removeEventListener(Runner.events.MOUSEUP, this); 612 document.removeEventListener(Runner.events.MOUSEUP, this);
613 } 613 }
614 }, 614 },
615 615
616 /** 616 /**
617 * Process keydown. 617 * Process keydown.
618 * @param {Event} e 618 * @param {Event} e
619 */ 619 */
620 onKeyDown: function(e) { 620 onKeyDown: function(e) {
621 // Prevent native page scrolling whilst tapping on mobile.
622 if (IS_MOBILE) {
623 e.preventDefault();
624 }
625
621 if (e.target != this.detailsButton) { 626 if (e.target != this.detailsButton) {
622 if (!this.crashed && (Runner.keycodes.JUMP[String(e.keyCode)] || 627 if (!this.crashed && (Runner.keycodes.JUMP[e.keyCode] ||
623 e.type == Runner.events.TOUCHSTART)) { 628 e.type == Runner.events.TOUCHSTART)) {
624 if (!this.activated) { 629 if (!this.activated) {
625 this.loadSounds(); 630 this.loadSounds();
626 this.activated = true; 631 this.activated = true;
632 errorPageController.trackEasterEgg();
627 } 633 }
628 634
629 if (!this.tRex.jumping) { 635 if (!this.tRex.jumping && !this.tRex.ducking) {
630 this.playSound(this.soundFx.BUTTON_PRESS); 636 this.playSound(this.soundFx.BUTTON_PRESS);
631 this.tRex.startJump(); 637 this.tRex.startJump(this.currentSpeed);
632 } 638 }
633 } 639 }
634 640
635 if (this.crashed && e.type == Runner.events.TOUCHSTART && 641 if (this.crashed && e.type == Runner.events.TOUCHSTART &&
636 e.currentTarget == this.containerEl) { 642 e.currentTarget == this.containerEl) {
637 this.restart(); 643 this.restart();
638 } 644 }
639 } 645 }
640 646
641 // Speed drop, activated only when jump key is not pressed. 647 if (this.activated && !this.crashed && Runner.keycodes.DUCK[e.keyCode]) {
642 if (Runner.keycodes.DUCK[e.keyCode] && this.tRex.jumping) {
643 e.preventDefault(); 648 e.preventDefault();
644 this.tRex.setSpeedDrop(); 649 if (this.tRex.jumping) {
650 // Speed drop, activated only when jump key is not pressed.
651 this.tRex.setSpeedDrop();
652 } else if (!this.tRex.jumping && !this.tRex.ducking) {
653 // Duck.
654 this.tRex.setDuck(true);
655 }
645 } 656 }
646 }, 657 },
647 658
648 659
649 /** 660 /**
650 * Process key up. 661 * Process key up.
651 * @param {Event} e 662 * @param {Event} e
652 */ 663 */
653 onKeyUp: function(e) { 664 onKeyUp: function(e) {
654 var keyCode = String(e.keyCode); 665 var keyCode = String(e.keyCode);
655 var isjumpKey = Runner.keycodes.JUMP[keyCode] || 666 var isjumpKey = Runner.keycodes.JUMP[keyCode] ||
656 e.type == Runner.events.TOUCHEND || 667 e.type == Runner.events.TOUCHEND ||
657 e.type == Runner.events.MOUSEDOWN; 668 e.type == Runner.events.MOUSEDOWN;
658 669
659 if (this.isRunning() && isjumpKey) { 670 if (this.isRunning() && isjumpKey) {
660 this.tRex.endJump(); 671 this.tRex.endJump();
661 } else if (Runner.keycodes.DUCK[keyCode]) { 672 } else if (Runner.keycodes.DUCK[keyCode]) {
662 this.tRex.speedDrop = false; 673 this.tRex.speedDrop = false;
674 this.tRex.setDuck(false);
663 } else if (this.crashed) { 675 } else if (this.crashed) {
664 // Check that enough time has elapsed before allowing jump key to restart. 676 // Check that enough time has elapsed before allowing jump key to restart.
665 var deltaTime = getTimeStamp() - this.time; 677 var deltaTime = getTimeStamp() - this.time;
666 678
667 if (Runner.keycodes.RESTART[keyCode] || 679 if (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) ||
668 (e.type == Runner.events.MOUSEUP && e.target == this.canvas) || 680 (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&
669 (deltaTime >= this.config.GAMEOVER_CLEAR_TIME && 681 Runner.keycodes.JUMP[keyCode])) {
670 Runner.keycodes.JUMP[keyCode])) {
671 this.restart(); 682 this.restart();
672 } 683 }
673 } else if (this.paused && isjumpKey) { 684 } else if (this.paused && isjumpKey) {
685 // Reset the jump state
686 this.tRex.reset();
674 this.play(); 687 this.play();
675 } 688 }
676 }, 689 },
677 690
678 /** 691 /**
692 * Returns whether the event was a left click on canvas.
693 * On Windows right click is registered as a click.
694 * @param {Event} e
695 * @return {boolean}
696 */
697 isLeftClickOnCanvas: function(e) {
698 return e.which && e.which < 2 && e.type == Runner.events.MOUSEUP &&
arv (Not doing code reviews) 2015/03/31 19:10:56 Use e.button instead of of e.which.
edwardjung 2015/04/01 10:40:46 Done.
699 e.target == this.canvas;
700 },
701
702 /**
679 * RequestAnimationFrame wrapper. 703 * RequestAnimationFrame wrapper.
680 */ 704 */
681 raq: function() { 705 raq: function() {
682 if (!this.drawPending) { 706 if (!this.drawPending) {
683 this.drawPending = true; 707 this.drawPending = true;
684 this.raqId = requestAnimationFrame(this.update.bind(this)); 708 this.raqId = requestAnimationFrame(this.update.bind(this));
685 } 709 }
686 }, 710 },
687 711
688 /** 712 /**
(...skipping 13 matching lines...) Expand all
702 726
703 this.stop(); 727 this.stop();
704 this.crashed = true; 728 this.crashed = true;
705 this.distanceMeter.acheivement = false; 729 this.distanceMeter.acheivement = false;
706 730
707 this.tRex.update(100, Trex.status.CRASHED); 731 this.tRex.update(100, Trex.status.CRASHED);
708 732
709 // Game over panel. 733 // Game over panel.
710 if (!this.gameOverPanel) { 734 if (!this.gameOverPanel) {
711 this.gameOverPanel = new GameOverPanel(this.canvas, 735 this.gameOverPanel = new GameOverPanel(this.canvas,
712 this.images.TEXT_SPRITE, this.images.RESTART, 736 this.spriteDef.TEXT_SPRITE, this.spriteDef.RESTART,
713 this.dimensions); 737 this.dimensions);
714 } else { 738 } else {
715 this.gameOverPanel.draw(); 739 this.gameOverPanel.draw();
716 } 740 }
717 741
718 // Update the high score. 742 // Update the high score.
719 if (this.distanceRan > this.highestScore) { 743 if (this.distanceRan > this.highestScore) {
720 this.highestScore = Math.ceil(this.distanceRan); 744 this.highestScore = Math.ceil(this.distanceRan);
721 this.distanceMeter.setHighScore(this.highestScore); 745 this.distanceMeter.setHighScore(this.highestScore);
722 } 746 }
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
805 Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) { 829 Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) {
806 var context = canvas.getContext('2d'); 830 var context = canvas.getContext('2d');
807 831
808 // Query the various pixel ratios 832 // Query the various pixel ratios
809 var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1; 833 var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;
810 var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1; 834 var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1;
811 var ratio = devicePixelRatio / backingStoreRatio; 835 var ratio = devicePixelRatio / backingStoreRatio;
812 836
813 // Upscale the canvas if the two ratios don't match 837 // Upscale the canvas if the two ratios don't match
814 if (devicePixelRatio !== backingStoreRatio) { 838 if (devicePixelRatio !== backingStoreRatio) {
815
816 var oldWidth = opt_width || canvas.width; 839 var oldWidth = opt_width || canvas.width;
817 var oldHeight = opt_height || canvas.height; 840 var oldHeight = opt_height || canvas.height;
818 841
819 canvas.width = oldWidth * ratio; 842 canvas.width = oldWidth * ratio;
820 canvas.height = oldHeight * ratio; 843 canvas.height = oldHeight * ratio;
821 844
822 canvas.style.width = oldWidth + 'px'; 845 canvas.style.width = oldWidth + 'px';
823 canvas.style.height = oldHeight + 'px'; 846 canvas.style.height = oldHeight + 'px';
824 847
825 // Scale the context to counter the fact that we've manually scaled 848 // Scale the context to counter the fact that we've manually scaled
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
903 return IS_IOS ? new Date().getTime() : performance.now(); 926 return IS_IOS ? new Date().getTime() : performance.now();
904 } 927 }
905 928
906 929
907 //****************************************************************************** 930 //******************************************************************************
908 931
909 932
910 /** 933 /**
911 * Game over panel. 934 * Game over panel.
912 * @param {!HTMLCanvasElement} canvas 935 * @param {!HTMLCanvasElement} canvas
913 * @param {!HTMLImage} textSprite 936 * @param {object} textImgPos
arv (Not doing code reviews) 2015/03/31 19:10:56 s/object/Object/
914 * @param {!HTMLImage} restartImg 937 * @param {object} restartImgPos
915 * @param {!Object} dimensions Canvas dimensions. 938 * @param {!Object} dimensions Canvas dimensions.
916 * @constructor 939 * @constructor
917 */ 940 */
918 function GameOverPanel(canvas, textSprite, restartImg, dimensions) { 941 function GameOverPanel(canvas, textImgPos, restartImgPos, dimensions) {
919 this.canvas = canvas; 942 this.canvas = canvas;
920 this.canvasCtx = canvas.getContext('2d'); 943 this.canvasCtx = canvas.getContext('2d');
921 this.canvasDimensions = dimensions; 944 this.canvasDimensions = dimensions;
922 this.textSprite = textSprite; 945 this.textImgPos = textImgPos;
923 this.restartImg = restartImg; 946 this.restartImgPos = restartImgPos;
924 this.draw(); 947 this.draw();
925 }; 948 };
926 949
927 950
928 /** 951 /**
929 * Dimensions used in the panel. 952 * Dimensions used in the panel.
930 * @enum {number} 953 * @enum {number}
931 */ 954 */
932 GameOverPanel.dimensions = { 955 GameOverPanel.dimensions = {
933 TEXT_X: 0, 956 TEXT_X: 0,
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
978 1001
979 if (IS_HIDPI) { 1002 if (IS_HIDPI) {
980 textSourceY *= 2; 1003 textSourceY *= 2;
981 textSourceX *= 2; 1004 textSourceX *= 2;
982 textSourceWidth *= 2; 1005 textSourceWidth *= 2;
983 textSourceHeight *= 2; 1006 textSourceHeight *= 2;
984 restartSourceWidth *= 2; 1007 restartSourceWidth *= 2;
985 restartSourceHeight *= 2; 1008 restartSourceHeight *= 2;
986 } 1009 }
987 1010
1011 textSourceX += this.textImgPos.x;
1012 textSourceY += this.textImgPos.y;
1013
988 // Game over text from sprite. 1014 // Game over text from sprite.
989 this.canvasCtx.drawImage(this.textSprite, 1015 this.canvasCtx.drawImage(Runner.imageSprite,
990 textSourceX, textSourceY, textSourceWidth, textSourceHeight, 1016 textSourceX, textSourceY, textSourceWidth, textSourceHeight,
991 textTargetX, textTargetY, textTargetWidth, textTargetHeight); 1017 textTargetX, textTargetY, textTargetWidth, textTargetHeight);
992 1018
993 // Restart button. 1019 // Restart button.
994 this.canvasCtx.drawImage(this.restartImg, 0, 0, 1020 this.canvasCtx.drawImage(Runner.imageSprite,
1021 this.restartImgPos.x, this.restartImgPos.y,
995 restartSourceWidth, restartSourceHeight, 1022 restartSourceWidth, restartSourceHeight,
996 restartTargetX, restartTargetY, dimensions.RESTART_WIDTH, 1023 restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
997 dimensions.RESTART_HEIGHT); 1024 dimensions.RESTART_HEIGHT);
998 } 1025 }
999 }; 1026 };
1000 1027
1001 1028
1002 //****************************************************************************** 1029 //******************************************************************************
1003 1030
1004 /** 1031 /**
(...skipping 22 matching lines...) Expand all
1027 obstacle.typeConfig.height - 2); 1054 obstacle.typeConfig.height - 2);
1028 1055
1029 // Debug outer box 1056 // Debug outer box
1030 if (opt_canvasCtx) { 1057 if (opt_canvasCtx) {
1031 drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox); 1058 drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
1032 } 1059 }
1033 1060
1034 // Simple outer bounds check. 1061 // Simple outer bounds check.
1035 if (boxCompare(tRexBox, obstacleBox)) { 1062 if (boxCompare(tRexBox, obstacleBox)) {
1036 var collisionBoxes = obstacle.collisionBoxes; 1063 var collisionBoxes = obstacle.collisionBoxes;
1037 var tRexCollisionBoxes = Trex.collisionBoxes; 1064 var tRexCollisionBoxes = tRex.ducking ?
1065 Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING;
1038 1066
1039 // Detailed axis aligned box check. 1067 // Detailed axis aligned box check.
1040 for (var t = 0; t < tRexCollisionBoxes.length; t++) { 1068 for (var t = 0; t < tRexCollisionBoxes.length; t++) {
1041 for (var i = 0; i < collisionBoxes.length; i++) { 1069 for (var i = 0; i < collisionBoxes.length; i++) {
1042 // Adjust the box to actual positions. 1070 // Adjust the box to actual positions.
1043 var adjTrexBox = 1071 var adjTrexBox =
1044 createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox); 1072 createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox);
1045 var adjObstacleBox = 1073 var adjObstacleBox =
1046 createAdjustedCollisionBox(collisionBoxes[i], obstacleBox); 1074 createAdjustedCollisionBox(collisionBoxes[i], obstacleBox);
1047 var crashed = boxCompare(adjTrexBox, adjObstacleBox); 1075 var crashed = boxCompare(adjTrexBox, adjObstacleBox);
(...skipping 27 matching lines...) Expand all
1075 box.height); 1103 box.height);
1076 }; 1104 };
1077 1105
1078 1106
1079 /** 1107 /**
1080 * Draw the collision boxes for debug. 1108 * Draw the collision boxes for debug.
1081 */ 1109 */
1082 function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) { 1110 function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) {
1083 canvasCtx.save(); 1111 canvasCtx.save();
1084 canvasCtx.strokeStyle = '#f00'; 1112 canvasCtx.strokeStyle = '#f00';
1085 canvasCtx.strokeRect(tRexBox.x, tRexBox.y, 1113 canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height);
1086 tRexBox.width, tRexBox.height);
1087 1114
1088 canvasCtx.strokeStyle = '#0f0'; 1115 canvasCtx.strokeStyle = '#0f0';
1089 canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y, 1116 canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,
1090 obstacleBox.width, obstacleBox.height); 1117 obstacleBox.width, obstacleBox.height);
1091 canvasCtx.restore(); 1118 canvasCtx.restore();
1092 }; 1119 };
1093 1120
1094 1121
1095 /** 1122 /**
1096 * Compare two collision boxes for a collision. 1123 * Compare two collision boxes for a collision.
1097 * @param {CollisionBox} tRexBox 1124 * @param {CollisionBox} tRexBox
1098 * @param {CollisionBox} obstacleBox 1125 * @param {CollisionBox} obstacleBox
1099 * @return {boolean} Whether the boxes intersected. 1126 * @return {boolean} Whether the boxes intersected.
1100 */ 1127 */
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
1134 this.height = h; 1161 this.height = h;
1135 }; 1162 };
1136 1163
1137 1164
1138 //****************************************************************************** 1165 //******************************************************************************
1139 1166
1140 /** 1167 /**
1141 * Obstacle. 1168 * Obstacle.
1142 * @param {HTMLCanvasCtx} canvasCtx 1169 * @param {HTMLCanvasCtx} canvasCtx
1143 * @param {Obstacle.type} type 1170 * @param {Obstacle.type} type
1144 * @param {image} obstacleImg Image sprite. 1171 * @param {Object} spritePos Obstacle position in sprite.
1145 * @param {Object} dimensions 1172 * @param {Object} dimensions
1146 * @param {number} gapCoefficient Mutipler in determining the gap. 1173 * @param {number} gapCoefficient Mutipler in determining the gap.
1147 * @param {number} speed 1174 * @param {number} speed
1148 */ 1175 */
1149 function Obstacle(canvasCtx, type, obstacleImg, dimensions, 1176 function Obstacle(canvasCtx, type, spriteImgPos, dimensions,
1150 gapCoefficient, speed) { 1177 gapCoefficient, speed) {
1151 1178
1152 this.canvasCtx = canvasCtx; 1179 this.canvasCtx = canvasCtx;
1153 this.image = obstacleImg; 1180 this.spritePos = spriteImgPos;
1154 this.typeConfig = type; 1181 this.typeConfig = type;
1155 this.gapCoefficient = gapCoefficient; 1182 this.gapCoefficient = gapCoefficient;
1156 this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH); 1183 this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH);
1157 this.dimensions = dimensions; 1184 this.dimensions = dimensions;
1158 this.remove = false; 1185 this.remove = false;
1159 this.xPos = 0; 1186 this.xPos = 0;
1160 this.yPos = this.typeConfig.yPos; 1187 this.yPos = 0;
1161 this.width = 0; 1188 this.width = 0;
1162 this.collisionBoxes = []; 1189 this.collisionBoxes = [];
1163 this.gap = 0; 1190 this.gap = 0;
1191 this.speedOffset = 0;
1192
1193 // For animated obstacles.
1194 this.currentFrame = 0;
1195 this.timer = 0;
1164 1196
1165 this.init(speed); 1197 this.init(speed);
1166 }; 1198 };
1167 1199
1168 /** 1200 /**
1169 * Coefficient for calculating the maximum gap. 1201 * Coefficient for calculating the maximum gap.
1170 * @const 1202 * @const
1171 */ 1203 */
1172 Obstacle.MAX_GAP_COEFFICIENT = 1.5; 1204 Obstacle.MAX_GAP_COEFFICIENT = 1.5;
1173 1205
(...skipping 13 matching lines...) Expand all
1187 this.cloneCollisionBoxes(); 1219 this.cloneCollisionBoxes();
1188 1220
1189 // Only allow sizing if we're at the right speed. 1221 // Only allow sizing if we're at the right speed.
1190 if (this.size > 1 && this.typeConfig.multipleSpeed > speed) { 1222 if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
1191 this.size = 1; 1223 this.size = 1;
1192 } 1224 }
1193 1225
1194 this.width = this.typeConfig.width * this.size; 1226 this.width = this.typeConfig.width * this.size;
1195 this.xPos = this.dimensions.WIDTH - this.width; 1227 this.xPos = this.dimensions.WIDTH - this.width;
1196 1228
1229 // Check if obstacle can be positioned at various heights.
1230 if (Array.isArray(this.typeConfig.yPos)) {
1231 var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile :
1232 this.typeConfig.yPos;
1233 this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
1234 } else {
1235 this.yPos = this.typeConfig.yPos;
1236 }
1237
1197 this.draw(); 1238 this.draw();
1198 1239
1199 // Make collision box adjustments, 1240 // Make collision box adjustments,
1200 // Central box is adjusted to the size as one box. 1241 // Central box is adjusted to the size as one box.
1201 // ____ ______ ________ 1242 // ____ ______ ________
1202 // _| |-| _| |-| _| |-| 1243 // _| |-| _| |-| _| |-|
1203 // | |<->| | | |<--->| | | |<----->| | 1244 // | |<->| | | |<--->| | | |<----->| |
1204 // | | 1 | | | | 2 | | | | 3 | | 1245 // | | 1 | | | | 2 | | | | 3 | |
1205 // |_|___|_| |_|_____|_| |_|_______|_| 1246 // |_|___|_| |_|_____|_| |_|_______|_|
1206 // 1247 //
1207 if (this.size > 1) { 1248 if (this.size > 1) {
1208 this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width - 1249 this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
1209 this.collisionBoxes[2].width; 1250 this.collisionBoxes[2].width;
1210 this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width; 1251 this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
1211 } 1252 }
1212 1253
1254 // For obstacles that go at a different speed from the horizon.
1255 if (this.typeConfig.speedOffset) {
1256 this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
1257 -this.typeConfig.speedOffset;
1258 }
1259
1213 this.gap = this.getGap(this.gapCoefficient, speed); 1260 this.gap = this.getGap(this.gapCoefficient, speed);
1214 }, 1261 },
1215 1262
1216 /** 1263 /**
1217 * Draw and crop based on size. 1264 * Draw and crop based on size.
1218 */ 1265 */
1219 draw: function() { 1266 draw: function() {
1220 var sourceWidth = this.typeConfig.width; 1267 var sourceWidth = this.typeConfig.width;
1221 var sourceHeight = this.typeConfig.height; 1268 var sourceHeight = this.typeConfig.height;
1222 1269
1223 if (IS_HIDPI) { 1270 if (IS_HIDPI) {
1224 sourceWidth = sourceWidth * 2; 1271 sourceWidth = sourceWidth * 2;
1225 sourceHeight = sourceHeight * 2; 1272 sourceHeight = sourceHeight * 2;
1226 } 1273 }
1227 1274
1228 // Sprite 1275 // X position in sprite.
1229 var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)); 1276 var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) +
1230 this.canvasCtx.drawImage(this.image, 1277 this.spritePos.x;
1231 sourceX, 0, 1278
1279 // Animation frames.
1280 if (this.currentFrame > 0) {
1281 sourceX += sourceWidth * this.currentFrame;
1282 }
1283
1284 this.canvasCtx.drawImage(Runner.imageSprite,
1285 sourceX, this.spritePos.y,
1232 sourceWidth * this.size, sourceHeight, 1286 sourceWidth * this.size, sourceHeight,
1233 this.xPos, this.yPos, 1287 this.xPos, this.yPos,
1234 this.typeConfig.width * this.size, this.typeConfig.height); 1288 this.typeConfig.width * this.size, this.typeConfig.height);
1235 }, 1289 },
1236 1290
1237 /** 1291 /**
1238 * Obstacle frame update. 1292 * Obstacle frame update.
1239 * @param {number} deltaTime 1293 * @param {number} deltaTime
1240 * @param {number} speed 1294 * @param {number} speed
1241 */ 1295 */
1242 update: function(deltaTime, speed) { 1296 update: function(deltaTime, speed) {
1243 if (!this.remove) { 1297 if (!this.remove) {
1298 if (this.typeConfig.speedOffset) {
1299 speed += this.speedOffset;
1300 }
1244 this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime); 1301 this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);
1302
1303 // Update frame
1304 if (this.typeConfig.numFrames) {
1305 this.timer += deltaTime;
1306 if (this.timer >= this.typeConfig.frameRate) {
1307 this.currentFrame =
1308 this.currentFrame == this.typeConfig.numFrames - 1 ?
1309 0 : this.currentFrame + 1;
1310 this.timer = 0;
1311 }
1312 }
1245 this.draw(); 1313 this.draw();
1246 1314
1247 if (!this.isVisible()) { 1315 if (!this.isVisible()) {
1248 this.remove = true; 1316 this.remove = true;
1249 } 1317 }
1250 } 1318 }
1251 }, 1319 },
1252 1320
1253 /** 1321 /**
1254 * Calculate a random gap size. 1322 * Calculate a random gap size.
(...skipping 30 matching lines...) Expand all
1285 collisionBoxes[i].height); 1353 collisionBoxes[i].height);
1286 } 1354 }
1287 } 1355 }
1288 }; 1356 };
1289 1357
1290 1358
1291 /** 1359 /**
1292 * Obstacle definitions. 1360 * Obstacle definitions.
1293 * minGap: minimum pixel space betweeen obstacles. 1361 * minGap: minimum pixel space betweeen obstacles.
1294 * multipleSpeed: Speed at which multiples are allowed. 1362 * multipleSpeed: Speed at which multiples are allowed.
1363 * speedOffset: speed faster / slower than the horizon.
1364 * minSpeed: Minimum speed which the obstacle can make an appearance.
1295 */ 1365 */
1296 Obstacle.types = [ 1366 Obstacle.types = [
1297 { 1367 {
1298 type: 'CACTUS_SMALL', 1368 type: 'CACTUS_SMALL',
1299 className: ' cactus cactus-small ',
1300 width: 17, 1369 width: 17,
1301 height: 35, 1370 height: 35,
1302 yPos: 105, 1371 yPos: 105,
1303 multipleSpeed: 3, 1372 multipleSpeed: 4,
1304 minGap: 120, 1373 minGap: 120,
1374 minSpeed: 0,
1305 collisionBoxes: [ 1375 collisionBoxes: [
1306 new CollisionBox(0, 7, 5, 27), 1376 new CollisionBox(0, 7, 5, 27),
1307 new CollisionBox(4, 0, 6, 34), 1377 new CollisionBox(4, 0, 6, 34),
1308 new CollisionBox(10, 4, 7, 14) 1378 new CollisionBox(10, 4, 7, 14)
1309 ] 1379 ]
1310 }, 1380 },
1311 { 1381 {
1312 type: 'CACTUS_LARGE', 1382 type: 'CACTUS_LARGE',
1313 className: ' cactus cactus-large ',
1314 width: 25, 1383 width: 25,
1315 height: 50, 1384 height: 50,
1316 yPos: 90, 1385 yPos: 90,
1317 multipleSpeed: 6, 1386 multipleSpeed: 7,
1318 minGap: 120, 1387 minGap: 120,
1388 minSpeed: 0,
1319 collisionBoxes: [ 1389 collisionBoxes: [
1320 new CollisionBox(0, 12, 7, 38), 1390 new CollisionBox(0, 12, 7, 38),
1321 new CollisionBox(8, 0, 7, 49), 1391 new CollisionBox(8, 0, 7, 49),
1322 new CollisionBox(13, 10, 10, 38) 1392 new CollisionBox(13, 10, 10, 38)
1323 ] 1393 ]
1394 },
1395 {
1396 type: 'PTERODACTYL',
arv (Not doing code reviews) 2015/03/31 19:10:56 New game elements. Exciting!
edwardjung 2015/04/01 10:40:47 Acknowledged.
1397 width: 46,
1398 height: 40,
1399 yPos: [ 100, 75, 50 ], // Variable height.
1400 yPosMobile: [ 100, 50 ], // Variable height mobile.
1401 multipleSpeed: 999,
1402 minSpeed: 8.5,
1403 minGap: 150,
1404 collisionBoxes: [
1405 new CollisionBox(15, 15, 16, 5),
1406 new CollisionBox(18, 21, 24, 6),
1407 new CollisionBox(2, 14, 4, 3),
1408 new CollisionBox(6, 10, 4, 7),
1409 new CollisionBox(10, 8, 6, 9)
1410 ],
1411 numFrames: 2,
1412 frameRate: 1000/6,
1413 speedOffset: .8
1324 } 1414 }
1325 ]; 1415 ];
1326 1416
1327 1417
1328 //****************************************************************************** 1418 //******************************************************************************
1329 /** 1419 /**
1330 * T-rex game character. 1420 * T-rex game character.
1331 * @param {HTMLCanvas} canvas 1421 * @param {HTMLCanvas} canvas
1332 * @param {HTMLImage} image Character image. 1422 * @param {Object} spritePos Positioning within image sprite.
1333 * @constructor 1423 * @constructor
1334 */ 1424 */
1335 function Trex(canvas, image) { 1425 function Trex(canvas, spritePos) {
1336 this.canvas = canvas; 1426 this.canvas = canvas;
1337 this.canvasCtx = canvas.getContext('2d'); 1427 this.canvasCtx = canvas.getContext('2d');
1338 this.image = image; 1428 this.spritePos = spritePos;
1339 this.xPos = 0; 1429 this.xPos = 0;
1340 this.yPos = 0; 1430 this.yPos = 0;
1341 // Position when on the ground. 1431 // Position when on the ground.
1342 this.groundYPos = 0; 1432 this.groundYPos = 0;
1343 this.currentFrame = 0; 1433 this.currentFrame = 0;
1344 this.currentAnimFrames = []; 1434 this.currentAnimFrames = [];
1345 this.blinkDelay = 0; 1435 this.blinkDelay = 0;
1346 this.animStartTime = 0; 1436 this.animStartTime = 0;
1347 this.timer = 0; 1437 this.timer = 0;
1348 this.msPerFrame = 1000 / FPS; 1438 this.msPerFrame = 1000 / FPS;
1349 this.config = Trex.config; 1439 this.config = Trex.config;
1350 // Current status. 1440 // Current status.
1351 this.status = Trex.status.WAITING; 1441 this.status = Trex.status.WAITING;
1352 1442
1353 this.jumping = false; 1443 this.jumping = false;
1444 this.ducking = false;
1354 this.jumpVelocity = 0; 1445 this.jumpVelocity = 0;
1355 this.reachedMinHeight = false; 1446 this.reachedMinHeight = false;
1356 this.speedDrop = false; 1447 this.speedDrop = false;
1357 this.jumpCount = 0; 1448 this.jumpCount = 0;
1358 this.jumpspotX = 0; 1449 this.jumpspotX = 0;
1359 1450
1360 this.init(); 1451 this.init();
1361 }; 1452 };
1362 1453
1363 1454
1364 /** 1455 /**
1365 * T-rex player config. 1456 * T-rex player config.
1366 * @enum {number} 1457 * @enum {number}
1367 */ 1458 */
1368 Trex.config = { 1459 Trex.config = {
1369 DROP_VELOCITY: -5, 1460 DROP_VELOCITY: -5,
1370 GRAVITY: 0.6, 1461 GRAVITY: 0.6,
1371 HEIGHT: 47, 1462 HEIGHT: 47,
1463 HEIGHT_DUCK: 25,
1372 INIITAL_JUMP_VELOCITY: -10, 1464 INIITAL_JUMP_VELOCITY: -10,
1373 INTRO_DURATION: 1500, 1465 INTRO_DURATION: 1500,
1374 MAX_JUMP_HEIGHT: 30, 1466 MAX_JUMP_HEIGHT: 30,
1375 MIN_JUMP_HEIGHT: 30, 1467 MIN_JUMP_HEIGHT: 30,
1376 SPEED_DROP_COEFFICIENT: 3, 1468 SPEED_DROP_COEFFICIENT: 3,
1377 SPRITE_WIDTH: 262, 1469 SPRITE_WIDTH: 262,
1378 START_X_POS: 50, 1470 START_X_POS: 50,
1379 WIDTH: 44 1471 WIDTH: 44,
1472 WIDTH_DUCK: 59
1380 }; 1473 };
1381 1474
1382 1475
1383 /** 1476 /**
1384 * Used in collision detection. 1477 * Used in collision detection.
1385 * @type {Array<CollisionBox>} 1478 * @type {Array<CollisionBox>}
1386 */ 1479 */
1387 Trex.collisionBoxes = [ 1480 Trex.collisionBoxes = {
1388 new CollisionBox(1, -1, 30, 26), 1481 DUCKING: [
1389 new CollisionBox(32, 0, 8, 16), 1482 new CollisionBox(1, 18, 55, 25)
1390 new CollisionBox(10, 35, 14, 8), 1483 ],
1391 new CollisionBox(1, 24, 29, 5), 1484 RUNNING: [
1392 new CollisionBox(5, 30, 21, 4), 1485 new CollisionBox(22, 0, 17, 16),
1393 new CollisionBox(9, 34, 15, 4) 1486 new CollisionBox(1, 18, 30, 9),
1394 ]; 1487 new CollisionBox(10, 35, 14, 8),
1488 new CollisionBox(1, 24, 29, 5),
1489 new CollisionBox(5, 30, 21, 4),
1490 new CollisionBox(9, 34, 15, 4)
1491 ]
1492 };
1395 1493
1396 1494
1397 /** 1495 /**
1398 * Animation states. 1496 * Animation states.
1399 * @enum {string} 1497 * @enum {string}
1400 */ 1498 */
1401 Trex.status = { 1499 Trex.status = {
1402 CRASHED: 'CRASHED', 1500 CRASHED: 'CRASHED',
1501 DUCKING: 'DUCKING',
1403 JUMPING: 'JUMPING', 1502 JUMPING: 'JUMPING',
1404 RUNNING: 'RUNNING', 1503 RUNNING: 'RUNNING',
1405 WAITING: 'WAITING' 1504 WAITING: 'WAITING'
1406 }; 1505 };
1407 1506
1408 /** 1507 /**
1409 * Blinking coefficient. 1508 * Blinking coefficient.
1410 * @const 1509 * @const
1411 */ 1510 */
1412 Trex.BLINK_TIMING = 7000; 1511 Trex.BLINK_TIMING = 7000;
(...skipping 12 matching lines...) Expand all
1425 frames: [88, 132], 1524 frames: [88, 132],
1426 msPerFrame: 1000 / 12 1525 msPerFrame: 1000 / 12
1427 }, 1526 },
1428 CRASHED: { 1527 CRASHED: {
1429 frames: [220], 1528 frames: [220],
1430 msPerFrame: 1000 / 60 1529 msPerFrame: 1000 / 60
1431 }, 1530 },
1432 JUMPING: { 1531 JUMPING: {
1433 frames: [0], 1532 frames: [0],
1434 msPerFrame: 1000 / 60 1533 msPerFrame: 1000 / 60
1534 },
1535 DUCKING: {
1536 frames: [262, 321],
1537 msPerFrame: 1000 / 8
1435 } 1538 }
1436 }; 1539 };
1437 1540
1438 1541
1439 Trex.prototype = { 1542 Trex.prototype = {
1440 /** 1543 /**
1441 * T-rex player initaliser. 1544 * T-rex player initaliser.
1442 * Sets the t-rex to blink at random intervals. 1545 * Sets the t-rex to blink at random intervals.
1443 */ 1546 */
1444 init: function() { 1547 init: function() {
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
1493 } else { 1596 } else {
1494 this.draw(this.currentAnimFrames[this.currentFrame], 0); 1597 this.draw(this.currentAnimFrames[this.currentFrame], 0);
1495 } 1598 }
1496 1599
1497 // Update the frame position. 1600 // Update the frame position.
1498 if (this.timer >= this.msPerFrame) { 1601 if (this.timer >= this.msPerFrame) {
1499 this.currentFrame = this.currentFrame == 1602 this.currentFrame = this.currentFrame ==
1500 this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1; 1603 this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
1501 this.timer = 0; 1604 this.timer = 0;
1502 } 1605 }
1606
1607 // Speed drop becomes duck if the down key is still being pressed.
1608 if (this.speedDrop && this.yPos == this.groundYPos) {
1609 this.speedDrop = false;
1610 this.setDuck(true);
1611 }
1503 }, 1612 },
1504 1613
1505 /** 1614 /**
1506 * Draw the t-rex to a particular position. 1615 * Draw the t-rex to a particular position.
1507 * @param {number} x 1616 * @param {number} x
1508 * @param {number} y 1617 * @param {number} y
1509 */ 1618 */
1510 draw: function(x, y) { 1619 draw: function(x, y) {
1511 var sourceX = x; 1620 var sourceX = x;
1512 var sourceY = y; 1621 var sourceY = y;
1513 var sourceWidth = this.config.WIDTH; 1622 var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
1623 this.config.WIDTH_DUCK : this.config.WIDTH;
1514 var sourceHeight = this.config.HEIGHT; 1624 var sourceHeight = this.config.HEIGHT;
1515 1625
1516 if (IS_HIDPI) { 1626 if (IS_HIDPI) {
1517 sourceX *= 2; 1627 sourceX *= 2;
1518 sourceY *= 2; 1628 sourceY *= 2;
1519 sourceWidth *= 2; 1629 sourceWidth *= 2;
1520 sourceHeight *= 2; 1630 sourceHeight *= 2;
1521 } 1631 }
1522 1632
1523 this.canvasCtx.drawImage(this.image, sourceX, sourceY, 1633 // Adjustments for sprite sheet position.
1524 sourceWidth, sourceHeight, 1634 sourceX += this.spritePos.x;
1525 this.xPos, this.yPos, 1635 sourceY += this.spritePos.y;
1526 this.config.WIDTH, this.config.HEIGHT); 1636
1637 // Ducking.
1638 if (this.ducking && this.status != Trex.status.CRASHED) {
1639 this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
1640 sourceWidth, sourceHeight,
1641 this.xPos, this.yPos,
1642 this.config.WIDTH_DUCK, this.config.HEIGHT);
1643 } else {
1644 // Crashed whilst ducking. Trex is standing up so needs adjustment.
1645 if (this.ducking && this.status == Trex.status.CRASHED) {
1646 this.xPos++;
1647 }
1648 // Standing / running
1649 this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
1650 sourceWidth, sourceHeight,
1651 this.xPos, this.yPos,
1652 this.config.WIDTH, this.config.HEIGHT);
1653 }
1527 }, 1654 },
1528 1655
1529 /** 1656 /**
1530 * Sets a random time for the blink to happen. 1657 * Sets a random time for the blink to happen.
1531 */ 1658 */
1532 setBlinkDelay: function() { 1659 setBlinkDelay: function() {
1533 this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING); 1660 this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
1534 }, 1661 },
1535 1662
1536 /** 1663 /**
1537 * Make t-rex blink at random intervals. 1664 * Make t-rex blink at random intervals.
1538 * @param {number} time Current time in milliseconds. 1665 * @param {number} time Current time in milliseconds.
1539 */ 1666 */
1540 blink: function(time) { 1667 blink: function(time) {
1541 var deltaTime = time - this.animStartTime; 1668 var deltaTime = time - this.animStartTime;
1542 1669
1543 if (deltaTime >= this.blinkDelay) { 1670 if (deltaTime >= this.blinkDelay) {
1544 this.draw(this.currentAnimFrames[this.currentFrame], 0); 1671 this.draw(this.currentAnimFrames[this.currentFrame], 0);
1545 1672
1546 if (this.currentFrame == 1) { 1673 if (this.currentFrame == 1) {
1547 // Set new random delay to blink. 1674 // Set new random delay to blink.
1548 this.setBlinkDelay(); 1675 this.setBlinkDelay();
1549 this.animStartTime = time; 1676 this.animStartTime = time;
1550 } 1677 }
1551 } 1678 }
1552 }, 1679 },
1553 1680
1554 /** 1681 /**
1555 * Initialise a jump. 1682 * Initialise a jump.
1683 * @param {number} speed
1556 */ 1684 */
1557 startJump: function() { 1685 startJump: function(speed) {
1558 if (!this.jumping) { 1686 if (!this.jumping) {
1559 this.update(0, Trex.status.JUMPING); 1687 this.update(0, Trex.status.JUMPING);
1560 this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY; 1688 // Tweak the jump velocity based on the speed.
1689 this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10);
1561 this.jumping = true; 1690 this.jumping = true;
1562 this.reachedMinHeight = false; 1691 this.reachedMinHeight = false;
1563 this.speedDrop = false; 1692 this.speedDrop = false;
1564 } 1693 }
1565 }, 1694 },
1566 1695
1567 /** 1696 /**
1568 * Jump is complete, falling down. 1697 * Jump is complete, falling down.
1569 */ 1698 */
1570 endJump: function() { 1699 endJump: function() {
1571 if (this.reachedMinHeight && 1700 if (this.reachedMinHeight &&
1572 this.jumpVelocity < this.config.DROP_VELOCITY) { 1701 this.jumpVelocity < this.config.DROP_VELOCITY) {
1573 this.jumpVelocity = this.config.DROP_VELOCITY; 1702 this.jumpVelocity = this.config.DROP_VELOCITY;
1574 } 1703 }
1575 }, 1704 },
1576 1705
1577 /** 1706 /**
1578 * Update frame for a jump. 1707 * Update frame for a jump.
1579 * @param {number} deltaTime 1708 * @param {number} deltaTime
1709 * @param {number} speed
1580 */ 1710 */
1581 updateJump: function(deltaTime) { 1711 updateJump: function(deltaTime, speed) {
1582 var msPerFrame = Trex.animFrames[this.status].msPerFrame; 1712 var msPerFrame = Trex.animFrames[this.status].msPerFrame;
1583 var framesElapsed = deltaTime / msPerFrame; 1713 var framesElapsed = deltaTime / msPerFrame;
1584 1714
1585 // Speed drop makes Trex fall faster. 1715 // Speed drop makes Trex fall faster.
1586 if (this.speedDrop) { 1716 if (this.speedDrop) {
1587 this.yPos += Math.round(this.jumpVelocity * 1717 this.yPos += Math.round(this.jumpVelocity *
1588 this.config.SPEED_DROP_COEFFICIENT * framesElapsed); 1718 this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
1589 } else { 1719 } else {
1590 this.yPos += Math.round(this.jumpVelocity * framesElapsed); 1720 this.yPos += Math.round(this.jumpVelocity * framesElapsed);
1591 } 1721 }
(...skipping 21 matching lines...) Expand all
1613 1743
1614 /** 1744 /**
1615 * Set the speed drop. Immediately cancels the current jump. 1745 * Set the speed drop. Immediately cancels the current jump.
1616 */ 1746 */
1617 setSpeedDrop: function() { 1747 setSpeedDrop: function() {
1618 this.speedDrop = true; 1748 this.speedDrop = true;
1619 this.jumpVelocity = 1; 1749 this.jumpVelocity = 1;
1620 }, 1750 },
1621 1751
1622 /** 1752 /**
1753 * @param {boolean} isDucking.
1754 */
1755 setDuck: function(isDucking) {
1756 if (isDucking && this.status != Trex.status.DUCKING) {
1757 this.update(0, Trex.status.DUCKING);
1758 this.ducking = true;
1759 } else if (this.status == Trex.status.DUCKING) {
1760 this.update(0, Trex.status.RUNNING);
1761 this.ducking = false;
1762 }
1763 },
1764
1765 /**
1623 * Reset the t-rex to running at start of game. 1766 * Reset the t-rex to running at start of game.
1624 */ 1767 */
1625 reset: function() { 1768 reset: function() {
1626 this.yPos = this.groundYPos; 1769 this.yPos = this.groundYPos;
1627 this.jumpVelocity = 0; 1770 this.jumpVelocity = 0;
1628 this.jumping = false; 1771 this.jumping = false;
1772 this.ducking = false;
1629 this.update(0, Trex.status.RUNNING); 1773 this.update(0, Trex.status.RUNNING);
1630 this.midair = false; 1774 this.midair = false;
1631 this.speedDrop = false; 1775 this.speedDrop = false;
1632 this.jumpCount = 0; 1776 this.jumpCount = 0;
1633 } 1777 }
1634 }; 1778 };
1635 1779
1636 1780
1637 //****************************************************************************** 1781 //******************************************************************************
1638 1782
1639 /** 1783 /**
1640 * Handles displaying the distance meter. 1784 * Handles displaying the distance meter.
1641 * @param {!HTMLCanvasElement} canvas 1785 * @param {!HTMLCanvasElement} canvas
1642 * @param {!HTMLImage} spriteSheet Image sprite. 1786 * @param {Object} spritePos Image position in sprite.
1643 * @param {number} canvasWidth 1787 * @param {number} canvasWidth
1644 * @constructor 1788 * @constructor
1645 */ 1789 */
1646 function DistanceMeter(canvas, spriteSheet, canvasWidth) { 1790 function DistanceMeter(canvas, spritePos, canvasWidth) {
1647 this.canvas = canvas; 1791 this.canvas = canvas;
1648 this.canvasCtx = canvas.getContext('2d'); 1792 this.canvasCtx = canvas.getContext('2d');
1649 this.image = spriteSheet; 1793 this.image = Runner.imageSprite;
1794 this.spritePos = spritePos;
1650 this.x = 0; 1795 this.x = 0;
1651 this.y = 5; 1796 this.y = 5;
1652 1797
1653 this.currentDistance = 0; 1798 this.currentDistance = 0;
1654 this.maxScore = 0; 1799 this.maxScore = 0;
1655 this.highScore = 0; 1800 this.highScore = 0;
1656 this.container = null; 1801 this.container = null;
1657 1802
1658 this.digits = []; 1803 this.digits = [];
1659 this.acheivement = false; 1804 this.acheivement = false;
1660 this.defaultString = ''; 1805 this.defaultString = '';
1661 this.flashTimer = 0; 1806 this.flashTimer = 0;
1662 this.flashIterations = 0; 1807 this.flashIterations = 0;
1663 1808
1664 this.config = DistanceMeter.config; 1809 this.config = DistanceMeter.config;
1810 this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS;
1665 this.init(canvasWidth); 1811 this.init(canvasWidth);
1666 }; 1812 };
1667 1813
1668 1814
1669 /** 1815 /**
1670 * @enum {number} 1816 * @enum {number}
1671 */ 1817 */
1672 DistanceMeter.dimensions = { 1818 DistanceMeter.dimensions = {
1673 WIDTH: 10, 1819 WIDTH: 10,
1674 HEIGHT: 13, 1820 HEIGHT: 13,
1675 DEST_WIDTH: 11 1821 DEST_WIDTH: 11
1676 }; 1822 };
1677 1823
1678 1824
1679 /** 1825 /**
1680 * Y positioning of the digits in the sprite sheet. 1826 * Y positioning of the digits in the sprite sheet.
1681 * X position is always 0. 1827 * X position is always 0.
1682 * @type {array<number>} 1828 * @type {Array<number>}
1683 */ 1829 */
1684 DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120]; 1830 DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];
1685 1831
1686 1832
1687 /** 1833 /**
1688 * Distance meter config. 1834 * Distance meter config.
1689 * @enum {number} 1835 * @enum {number}
1690 */ 1836 */
1691 DistanceMeter.config = { 1837 DistanceMeter.config = {
1692 // Number of digits. 1838 // Number of digits.
(...skipping 15 matching lines...) Expand all
1708 1854
1709 DistanceMeter.prototype = { 1855 DistanceMeter.prototype = {
1710 /** 1856 /**
1711 * Initialise the distance meter to '00000'. 1857 * Initialise the distance meter to '00000'.
1712 * @param {number} width Canvas width in px. 1858 * @param {number} width Canvas width in px.
1713 */ 1859 */
1714 init: function(width) { 1860 init: function(width) {
1715 var maxDistanceStr = ''; 1861 var maxDistanceStr = '';
1716 1862
1717 this.calcXPos(width); 1863 this.calcXPos(width);
1718 this.maxScore = this.config.MAX_DISTANCE_UNITS; 1864 this.maxScore = this.maxScoreUnits;
1719 for (var i = 0; i < this.config.MAX_DISTANCE_UNITS; i++) { 1865 for (var i = 0; i < this.maxScoreUnits; i++) {
1720 this.draw(i, 0); 1866 this.draw(i, 0);
1721 this.defaultString += '0'; 1867 this.defaultString += '0';
1722 maxDistanceStr += '9'; 1868 maxDistanceStr += '9';
1723 } 1869 }
1724 1870
1725 this.maxScore = parseInt(maxDistanceStr); 1871 this.maxScore = parseInt(maxDistanceStr);
1726 }, 1872 },
1727 1873
1728 /** 1874 /**
1729 * Calculate the xPos in the canvas. 1875 * Calculate the xPos in the canvas.
1730 * @param {number} canvasWidth 1876 * @param {number} canvasWidth
1731 */ 1877 */
1732 calcXPos: function(canvasWidth) { 1878 calcXPos: function(canvasWidth) {
1733 this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH * 1879 this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
1734 (this.config.MAX_DISTANCE_UNITS + 1)); 1880 (this.maxScoreUnits + 1));
1735 }, 1881 },
1736 1882
1737 /** 1883 /**
1738 * Draw a digit to canvas. 1884 * Draw a digit to canvas.
1739 * @param {number} digitPos Position of the digit. 1885 * @param {number} digitPos Position of the digit.
1740 * @param {number} value Digit value 0-9. 1886 * @param {number} value Digit value 0-9.
1741 * @param {boolean} opt_highScore Whether drawing the high score. 1887 * @param {boolean} opt_highScore Whether drawing the high score.
1742 */ 1888 */
1743 draw: function(digitPos, value, opt_highScore) { 1889 draw: function(digitPos, value, opt_highScore) {
1744 var sourceWidth = DistanceMeter.dimensions.WIDTH; 1890 var sourceWidth = DistanceMeter.dimensions.WIDTH;
1745 var sourceHeight = DistanceMeter.dimensions.HEIGHT; 1891 var sourceHeight = DistanceMeter.dimensions.HEIGHT;
1746 var sourceX = DistanceMeter.dimensions.WIDTH * value; 1892 var sourceX = DistanceMeter.dimensions.WIDTH * value;
1893 var sourceY = 0;
1747 1894
1748 var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH; 1895 var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
1749 var targetY = this.y; 1896 var targetY = this.y;
1750 var targetWidth = DistanceMeter.dimensions.WIDTH; 1897 var targetWidth = DistanceMeter.dimensions.WIDTH;
1751 var targetHeight = DistanceMeter.dimensions.HEIGHT; 1898 var targetHeight = DistanceMeter.dimensions.HEIGHT;
1752 1899
1753 // For high DPI we 2x source values. 1900 // For high DPI we 2x source values.
1754 if (IS_HIDPI) { 1901 if (IS_HIDPI) {
1755 sourceWidth *= 2; 1902 sourceWidth *= 2;
1756 sourceHeight *= 2; 1903 sourceHeight *= 2;
1757 sourceX *= 2; 1904 sourceX *= 2;
1758 } 1905 }
1759 1906
1907 sourceX += this.spritePos.x;
1908 sourceY += this.spritePos.y;
1909
1760 this.canvasCtx.save(); 1910 this.canvasCtx.save();
1761 1911
1762 if (opt_highScore) { 1912 if (opt_highScore) {
1763 // Left of the current score. 1913 // Left of the current score.
1764 var highScoreX = this.x - (this.config.MAX_DISTANCE_UNITS * 2) * 1914 var highScoreX = this.x - (this.maxScoreUnits * 2) *
1765 DistanceMeter.dimensions.WIDTH; 1915 DistanceMeter.dimensions.WIDTH;
1766 this.canvasCtx.translate(highScoreX, this.y); 1916 this.canvasCtx.translate(highScoreX, this.y);
1767 } else { 1917 } else {
1768 this.canvasCtx.translate(this.x, this.y); 1918 this.canvasCtx.translate(this.x, this.y);
1769 } 1919 }
1770 1920
1771 this.canvasCtx.drawImage(this.image, sourceX, 0, 1921 this.canvasCtx.drawImage(this.image, sourceX, sourceY,
1772 sourceWidth, sourceHeight, 1922 sourceWidth, sourceHeight,
1773 targetX, targetY, 1923 targetX, targetY,
1774 targetWidth, targetHeight 1924 targetWidth, targetHeight
1775 ); 1925 );
1776 1926
1777 this.canvasCtx.restore(); 1927 this.canvasCtx.restore();
1778 }, 1928 },
1779 1929
1780 /** 1930 /**
1781 * Covert pixel distance to a 'real' distance. 1931 * Covert pixel distance to a 'real' distance.
1782 * @param {number} distance Pixel distance ran. 1932 * @param {number} distance Pixel distance ran.
1783 * @return {number} The 'real' distance ran. 1933 * @return {number} The 'real' distance ran.
1784 */ 1934 */
1785 getActualDistance: function(distance) { 1935 getActualDistance: function(distance) {
1786 return distance ? 1936 return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
1787 Math.round(distance * this.config.COEFFICIENT) : 0;
1788 }, 1937 },
1789 1938
1790 /** 1939 /**
1791 * Update the distance meter. 1940 * Update the distance meter.
1941 * @param {number} distance
1792 * @param {number} deltaTime 1942 * @param {number} deltaTime
1793 * @param {number} distance
1794 * @return {boolean} Whether the acheivement sound fx should be played. 1943 * @return {boolean} Whether the acheivement sound fx should be played.
1795 */ 1944 */
1796 update: function(deltaTime, distance) { 1945 update: function(deltaTime, distance) {
1797 var paint = true; 1946 var paint = true;
1798 var playSound = false; 1947 var playSound = false;
1799 1948
1800 if (!this.acheivement) { 1949 if (!this.acheivement) {
1801 distance = this.getActualDistance(distance); 1950 distance = this.getActualDistance(distance);
1802 1951
1952 // Score has gone beyond the initial digit count.
1953 if (distance > this.maxScore && this.maxScoreUnits ==
1954 this.config.MAX_DISTANCE_UNITS) {
1955 this.maxScoreUnits++;
1956 this.maxScore = parseInt(this.maxScore + '9');
1957 } else {
1958 this.distance = 0;
1959 }
1960
1803 if (distance > 0) { 1961 if (distance > 0) {
1804 // Acheivement unlocked 1962 // Acheivement unlocked
1805 if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) { 1963 if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) {
1806 // Flash score and play sound. 1964 // Flash score and play sound.
1807 this.acheivement = true; 1965 this.acheivement = true;
1808 this.flashTimer = 0; 1966 this.flashTimer = 0;
1809 playSound = true; 1967 playSound = true;
1810 } 1968 }
1811 1969
1812 // Create a string representation of the distance with leading 0. 1970 // Create a string representation of the distance with leading 0.
1813 var distanceStr = (this.defaultString + 1971 var distanceStr = (this.defaultString +
1814 distance).substr(-this.config.MAX_DISTANCE_UNITS); 1972 distance).substr(-this.maxScoreUnits);
1815 this.digits = distanceStr.split(''); 1973 this.digits = distanceStr.split('');
1816 } else { 1974 } else {
1817 this.digits = this.defaultString.split(''); 1975 this.digits = this.defaultString.split('');
1818 } 1976 }
1819 } else { 1977 } else {
1820 // Control flashing of the score on reaching acheivement. 1978 // Control flashing of the score on reaching acheivement.
1821 if (this.flashIterations <= this.config.FLASH_ITERATIONS) { 1979 if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
1822 this.flashTimer += deltaTime; 1980 this.flashTimer += deltaTime;
1823 1981
1824 if (this.flashTimer < this.config.FLASH_DURATION) { 1982 if (this.flashTimer < this.config.FLASH_DURATION) {
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
1860 }, 2018 },
1861 2019
1862 /** 2020 /**
1863 * Set the highscore as a array string. 2021 * Set the highscore as a array string.
1864 * Position of char in the sprite: H - 10, I - 11. 2022 * Position of char in the sprite: H - 10, I - 11.
1865 * @param {number} distance Distance ran in pixels. 2023 * @param {number} distance Distance ran in pixels.
1866 */ 2024 */
1867 setHighScore: function(distance) { 2025 setHighScore: function(distance) {
1868 distance = this.getActualDistance(distance); 2026 distance = this.getActualDistance(distance);
1869 var highScoreStr = (this.defaultString + 2027 var highScoreStr = (this.defaultString +
1870 distance).substr(-this.config.MAX_DISTANCE_UNITS); 2028 distance).substr(-this.maxScoreUnits);
1871 2029
1872 this.highScore = ['10', '11', ''].concat(highScoreStr.split('')); 2030 this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
1873 }, 2031 },
1874 2032
1875 /** 2033 /**
1876 * Reset the distance meter back to '00000'. 2034 * Reset the distance meter back to '00000'.
1877 */ 2035 */
1878 reset: function() { 2036 reset: function() {
1879 this.update(0); 2037 this.update(0);
1880 this.acheivement = false; 2038 this.acheivement = false;
1881 } 2039 }
1882 }; 2040 };
1883 2041
1884 2042
1885 //****************************************************************************** 2043 //******************************************************************************
1886 2044
1887 /** 2045 /**
1888 * Cloud background item. 2046 * Cloud background item.
1889 * Similar to an obstacle object but without collision boxes. 2047 * Similar to an obstacle object but without collision boxes.
1890 * @param {HTMLCanvasElement} canvas Canvas element. 2048 * @param {HTMLCanvasElement} canvas Canvas element.
1891 * @param {Image} cloudImg 2049 * @param {Object} spritePos Position of image in sprite.
1892 * @param {number} containerWidth 2050 * @param {number} containerWidth
1893 */ 2051 */
1894 function Cloud(canvas, cloudImg, containerWidth) { 2052 function Cloud(canvas, spritePos, containerWidth) {
1895 this.canvas = canvas; 2053 this.canvas = canvas;
1896 this.canvasCtx = this.canvas.getContext('2d'); 2054 this.canvasCtx = this.canvas.getContext('2d');
1897 this.image = cloudImg; 2055 this.spritePos = spritePos;
1898 this.containerWidth = containerWidth; 2056 this.containerWidth = containerWidth;
1899 this.xPos = containerWidth; 2057 this.xPos = containerWidth;
1900 this.yPos = 0; 2058 this.yPos = 0;
1901 this.remove = false; 2059 this.remove = false;
1902 this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP, 2060 this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP,
1903 Cloud.config.MAX_CLOUD_GAP); 2061 Cloud.config.MAX_CLOUD_GAP);
1904 2062
1905 this.init(); 2063 this.init();
1906 }; 2064 };
1907 2065
(...skipping 28 matching lines...) Expand all
1936 draw: function() { 2094 draw: function() {
1937 this.canvasCtx.save(); 2095 this.canvasCtx.save();
1938 var sourceWidth = Cloud.config.WIDTH; 2096 var sourceWidth = Cloud.config.WIDTH;
1939 var sourceHeight = Cloud.config.HEIGHT; 2097 var sourceHeight = Cloud.config.HEIGHT;
1940 2098
1941 if (IS_HIDPI) { 2099 if (IS_HIDPI) {
1942 sourceWidth = sourceWidth * 2; 2100 sourceWidth = sourceWidth * 2;
1943 sourceHeight = sourceHeight * 2; 2101 sourceHeight = sourceHeight * 2;
1944 } 2102 }
1945 2103
1946 this.canvasCtx.drawImage(this.image, 0, 0, 2104 this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x,
2105 this.spritePos.y,
1947 sourceWidth, sourceHeight, 2106 sourceWidth, sourceHeight,
1948 this.xPos, this.yPos, 2107 this.xPos, this.yPos,
1949 Cloud.config.WIDTH, Cloud.config.HEIGHT); 2108 Cloud.config.WIDTH, Cloud.config.HEIGHT);
1950 2109
1951 this.canvasCtx.restore(); 2110 this.canvasCtx.restore();
1952 }, 2111 },
1953 2112
1954 /** 2113 /**
1955 * Update the cloud position. 2114 * Update the cloud position.
1956 * @param {number} speed 2115 * @param {number} speed
(...skipping 19 matching lines...) Expand all
1976 } 2135 }
1977 }; 2136 };
1978 2137
1979 2138
1980 //****************************************************************************** 2139 //******************************************************************************
1981 2140
1982 /** 2141 /**
1983 * Horizon Line. 2142 * Horizon Line.
1984 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon. 2143 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
1985 * @param {HTMLCanvasElement} canvas 2144 * @param {HTMLCanvasElement} canvas
1986 * @param {HTMLImage} bgImg Horizon line sprite. 2145 * @param {Object} spritePos Horizon position in sprite.
1987 * @constructor 2146 * @constructor
1988 */ 2147 */
1989 function HorizonLine(canvas, bgImg) { 2148 function HorizonLine(canvas, spritePos) {
1990 this.image = bgImg; 2149 this.spritePos = spritePos;
1991 this.canvas = canvas; 2150 this.canvas = canvas;
1992 this.canvasCtx = canvas.getContext('2d'); 2151 this.canvasCtx = canvas.getContext('2d');
1993 this.sourceDimensions = {}; 2152 this.sourceDimensions = {};
1994 this.dimensions = HorizonLine.dimensions; 2153 this.dimensions = HorizonLine.dimensions;
1995 this.sourceXPos = [0, this.dimensions.WIDTH]; 2154 this.sourceXPos = [this.spritePos.x, this.spritePos.x +
2155 this.dimensions.WIDTH];
1996 this.xPos = []; 2156 this.xPos = [];
1997 this.yPos = 0; 2157 this.yPos = 0;
1998 this.bumpThreshold = 0.5; 2158 this.bumpThreshold = 0.5;
1999 2159
2000 this.setSourceDimensions(); 2160 this.setSourceDimensions();
2001 this.draw(); 2161 this.draw();
2002 }; 2162 };
2003 2163
2004 2164
2005 /** 2165 /**
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
2040 * Return the crop x position of a type. 2200 * Return the crop x position of a type.
2041 */ 2201 */
2042 getRandomType: function() { 2202 getRandomType: function() {
2043 return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0; 2203 return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
2044 }, 2204 },
2045 2205
2046 /** 2206 /**
2047 * Draw the horizon line. 2207 * Draw the horizon line.
2048 */ 2208 */
2049 draw: function() { 2209 draw: function() {
2050 this.canvasCtx.drawImage(this.image, this.sourceXPos[0], 0, 2210 this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0],
2211 this.spritePos.y,
2051 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT, 2212 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
2052 this.xPos[0], this.yPos, 2213 this.xPos[0], this.yPos,
2053 this.dimensions.WIDTH, this.dimensions.HEIGHT); 2214 this.dimensions.WIDTH, this.dimensions.HEIGHT);
2054 2215
2055 this.canvasCtx.drawImage(this.image, this.sourceXPos[1], 0, 2216 this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1],
2217 this.spritePos.y,
2056 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT, 2218 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
2057 this.xPos[1], this.yPos, 2219 this.xPos[1], this.yPos,
2058 this.dimensions.WIDTH, this.dimensions.HEIGHT); 2220 this.dimensions.WIDTH, this.dimensions.HEIGHT);
2059 }, 2221 },
2060 2222
2061 /** 2223 /**
2062 * Update the x position of an indivdual piece of the line. 2224 * Update the x position of an indivdual piece of the line.
2063 * @param {number} pos Line position. 2225 * @param {number} pos Line position.
2064 * @param {number} increment 2226 * @param {number} increment
2065 */ 2227 */
2066 updateXPos: function(pos, increment) { 2228 updateXPos: function(pos, increment) {
2067 var line1 = pos; 2229 var line1 = pos;
2068 var line2 = pos == 0 ? 1 : 0; 2230 var line2 = pos == 0 ? 1 : 0;
2069 2231
2070 this.xPos[line1] -= increment; 2232 this.xPos[line1] -= increment;
2071 this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH; 2233 this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
2072 2234
2073 if (this.xPos[line1] <= -this.dimensions.WIDTH) { 2235 if (this.xPos[line1] <= -this.dimensions.WIDTH) {
2074 this.xPos[line1] += this.dimensions.WIDTH * 2; 2236 this.xPos[line1] += this.dimensions.WIDTH * 2;
2075 this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH; 2237 this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
2076 this.sourceXPos[line1] = this.getRandomType(); 2238 this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;
2077 } 2239 }
2078 }, 2240 },
2079 2241
2080 /** 2242 /**
2081 * Update the horizon line. 2243 * Update the horizon line.
2082 * @param {number} deltaTime 2244 * @param {number} deltaTime
2083 * @param {number} speed 2245 * @param {number} speed
2084 */ 2246 */
2085 update: function(deltaTime, speed) { 2247 update: function(deltaTime, speed) {
2086 var increment = Math.floor(speed * (FPS / 1000) * deltaTime); 2248 var increment = Math.floor(speed * (FPS / 1000) * deltaTime);
(...skipping 14 matching lines...) Expand all
2101 this.xPos[1] = HorizonLine.dimensions.WIDTH; 2263 this.xPos[1] = HorizonLine.dimensions.WIDTH;
2102 } 2264 }
2103 }; 2265 };
2104 2266
2105 2267
2106 //****************************************************************************** 2268 //******************************************************************************
2107 2269
2108 /** 2270 /**
2109 * Horizon background class. 2271 * Horizon background class.
2110 * @param {HTMLCanvasElement} canvas 2272 * @param {HTMLCanvasElement} canvas
2111 * @param {Array<HTMLImageElement>} images 2273 * @param {Object} spritePos Sprite positioning.
2112 * @param {object} dimensions Canvas dimensions. 2274 * @param {object} dimensions Canvas dimensions.
arv (Not doing code reviews) 2015/03/31 19:10:56 typo here too
edwardjung 2015/04/01 10:40:46 Done.
2113 * @param {number} gapCoefficient 2275 * @param {number} gapCoefficient
2114 * @constructor 2276 * @constructor
2115 */ 2277 */
2116 function Horizon(canvas, images, dimensions, gapCoefficient) { 2278 function Horizon(canvas, spritePos, dimensions, gapCoefficient) {
2117 this.canvas = canvas; 2279 this.canvas = canvas;
2118 this.canvasCtx = this.canvas.getContext('2d'); 2280 this.canvasCtx = this.canvas.getContext('2d');
2119 this.config = Horizon.config; 2281 this.config = Horizon.config;
2120 this.dimensions = dimensions; 2282 this.dimensions = dimensions;
2121 this.gapCoefficient = gapCoefficient; 2283 this.gapCoefficient = gapCoefficient;
2122 this.obstacles = []; 2284 this.obstacles = [];
2285 this.obstacleHistory = [];
2123 this.horizonOffsets = [0, 0]; 2286 this.horizonOffsets = [0, 0];
2124 this.cloudFrequency = this.config.CLOUD_FREQUENCY; 2287 this.cloudFrequency = this.config.CLOUD_FREQUENCY;
2288 this.spritePos = spritePos;
2125 2289
2126 // Cloud 2290 // Cloud
2127 this.clouds = []; 2291 this.clouds = [];
2128 this.cloudImg = images.CLOUD;
2129 this.cloudSpeed = this.config.BG_CLOUD_SPEED; 2292 this.cloudSpeed = this.config.BG_CLOUD_SPEED;
2130 2293
2131 // Horizon 2294 // Horizon
2132 this.horizonImg = images.HORIZON;
2133 this.horizonLine = null; 2295 this.horizonLine = null;
2134 2296
2135 // Obstacles
2136 this.obstacleImgs = {
2137 CACTUS_SMALL: images.CACTUS_SMALL,
2138 CACTUS_LARGE: images.CACTUS_LARGE
2139 };
2140
2141 this.init(); 2297 this.init();
2142 }; 2298 };
2143 2299
2144 2300
2145 /** 2301 /**
2146 * Horizon config. 2302 * Horizon config.
2147 * @enum {number} 2303 * @enum {number}
2148 */ 2304 */
2149 Horizon.config = { 2305 Horizon.config = {
2150 BG_CLOUD_SPEED: 0.2, 2306 BG_CLOUD_SPEED: 0.2,
2151 BUMPY_THRESHOLD: .3, 2307 BUMPY_THRESHOLD: .3,
2152 CLOUD_FREQUENCY: .5, 2308 CLOUD_FREQUENCY: .5,
2153 HORIZON_HEIGHT: 16, 2309 HORIZON_HEIGHT: 16,
2154 MAX_CLOUDS: 6 2310 MAX_CLOUDS: 6
2155 }; 2311 };
2156 2312
2157 2313
2158 Horizon.prototype = { 2314 Horizon.prototype = {
2159 /** 2315 /**
2160 * Initialise the horizon. Just add the line and a cloud. No obstacles. 2316 * Initialise the horizon. Just add the line and a cloud. No obstacles.
2161 */ 2317 */
2162 init: function() { 2318 init: function() {
2163 this.addCloud(); 2319 this.addCloud();
2164 this.horizonLine = new HorizonLine(this.canvas, this.horizonImg); 2320 this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON);
2165 }, 2321 },
2166 2322
2167 /** 2323 /**
2168 * @param {number} deltaTime 2324 * @param {number} deltaTime
2169 * @param {number} currentSpeed 2325 * @param {number} currentSpeed
2170 * @param {boolean} updateObstacles Used as an override to prevent 2326 * @param {boolean} updateObstacles Used as an override to prevent
2171 * the obstacles from being updated / added. This happens in the 2327 * the obstacles from being updated / added. This happens in the
2172 * ease in section. 2328 * ease in section.
2173 */ 2329 */
2174 update: function(deltaTime, currentSpeed, updateObstacles) { 2330 update: function(deltaTime, currentSpeed, updateObstacles) {
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
2245 // Create new obstacles. 2401 // Create new obstacles.
2246 this.addNewObstacle(currentSpeed); 2402 this.addNewObstacle(currentSpeed);
2247 } 2403 }
2248 }, 2404 },
2249 2405
2250 /** 2406 /**
2251 * Add a new obstacle. 2407 * Add a new obstacle.
2252 * @param {number} currentSpeed 2408 * @param {number} currentSpeed
2253 */ 2409 */
2254 addNewObstacle: function(currentSpeed) { 2410 addNewObstacle: function(currentSpeed) {
2255 var obstacleTypeIndex = 2411 var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1);
2256 getRandomNum(0, Obstacle.types.length - 1);
2257 var obstacleType = Obstacle.types[obstacleTypeIndex]; 2412 var obstacleType = Obstacle.types[obstacleTypeIndex];
2258 var obstacleImg = this.obstacleImgs[obstacleType.type];
2259 2413
2260 this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType, 2414 // Check for multiples of the same type of obstacle.
2261 obstacleImg, this.dimensions, this.gapCoefficient, currentSpeed)); 2415 // Also check obstacle is available at current speed.
2416 if (this.duplicateObstacleCheck(obstacleType.type) ||
2417 currentSpeed < obstacleType.minSpeed) {
2418 this.addNewObstacle(currentSpeed);
2419 } else {
2420 var obstacleSpritePos = this.spritePos[obstacleType.type];
2421
2422 this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType,
2423 obstacleSpritePos, this.dimensions,
2424 this.gapCoefficient, currentSpeed));
2425
2426 this.obstacleHistory.unshift(obstacleType.type);
2427
2428 if (this.obstacleHistory.length > 1) {
2429 this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION);
2430 }
2431 }
2262 }, 2432 },
2263 2433
2264 /** 2434 /**
2435 * Returns whether the previous two obstacles are the same as the next one.
2436 * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION.
2437 * @return {boolean}
2438 */
2439 duplicateObstacleCheck: function(nextObstacleType) {
2440 var duplicateCount = 0;
2441
2442 for (var i = 0; i < this.obstacleHistory.length; i++) {
2443 duplicateCount = this.obstacleHistory[i] == nextObstacleType ?
2444 duplicateCount + 1 : 0;
2445 }
2446 return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION;
2447 },
2448
2449 /**
2265 * Reset the horizon layer. 2450 * Reset the horizon layer.
2266 * Remove existing obstacles and reposition the horizon line. 2451 * Remove existing obstacles and reposition the horizon line.
2267 */ 2452 */
2268 reset: function() { 2453 reset: function() {
2269 this.obstacles = []; 2454 this.obstacles = [];
2270 this.horizonLine.reset(); 2455 this.horizonLine.reset();
2271 }, 2456 },
2272 2457
2273 /** 2458 /**
2274 * Update the canvas width and scaling. 2459 * Update the canvas width and scaling.
2275 * @param {number} width Canvas width. 2460 * @param {number} width Canvas width.
2276 * @param {number} height Canvas height. 2461 * @param {number} height Canvas height.
2277 */ 2462 */
2278 resize: function(width, height) { 2463 resize: function(width, height) {
2279 this.canvas.width = width; 2464 this.canvas.width = width;
2280 this.canvas.height = height; 2465 this.canvas.height = height;
2281 }, 2466 },
2282 2467
2283 /** 2468 /**
2284 * Add a new cloud to the horizon. 2469 * Add a new cloud to the horizon.
2285 */ 2470 */
2286 addCloud: function() { 2471 addCloud: function() {
2287 this.clouds.push(new Cloud(this.canvas, this.cloudImg, 2472 this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD,
2288 this.dimensions.WIDTH)); 2473 this.dimensions.WIDTH));
2289 } 2474 }
2290 }; 2475 };
2291 })(); 2476 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698