Şimdi Ara

Google dinazor oyunu

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

    < Bu ileti mobil sürüm kullanılarak atıldı >







  • işsizsin diyenler buradan eksileyebilir...
  • quote:

    Orijinalden alıntı: takıntılı bir adam

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


    Bu nasıl oyun böyle kardeş




  • dinazorlar ben zorlamam
  • quote:

    Orijinalden alıntı: takıntılı bir adam

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


    bu ne la




  • But first,let me take selfie

    < Bu ileti mini sürüm kullanılarak atıldı >
  • Orijinalden alıntı: takıntılı bir adam

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



    < Bu mesaj bu kişi tarafından değiştirildi craigdizzeer -- 26 Ocak 2015; 19:03:41 >
    < Bu ileti mobil sürüm kullanılarak atıldı >




  • 
Sayfa: 1
- x
Bildirim
mesajınız kopyalandı (ctrl+v) yapıştırmak istediğiniz yere yapıştırabilirsiniz.