core.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. /**
  2. * Core
  3. * MIT licensed
  4. *
  5. * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
  6. */
  7. var Core = new function(){
  8. // Flags if we are running mobile mode
  9. var isMobile = !!navigator.userAgent.toLowerCase().match( /ipod|ipad|iphone|android/gi );
  10. var DEFAULT_WIDTH = 1000,
  11. DEFAULT_HEIGHT = 650,
  12. BORDER_WIDTH = 6,
  13. FRAMERATE = 60;
  14. // Types of organisms
  15. var ORGANISM_ENEMY = 'enemy',
  16. ORGANISM_ENERGY = 'energy';
  17. // The world dimensions
  18. var world = {
  19. width: isMobile ? window.innerWidth : DEFAULT_WIDTH,
  20. height: isMobile ? window.innerHeight : DEFAULT_HEIGHT
  21. };
  22. var canvas,
  23. context;
  24. var canvasBackground,
  25. contextBackground;
  26. // UI DOM elements
  27. var status;
  28. var panels;
  29. var message;
  30. var title;
  31. var startButton;
  32. // Game elements
  33. var organisms = [];
  34. var particles = [];
  35. var player;
  36. // Mouse properties
  37. var mouseX = (window.innerWidth + world.width) * 0.5;
  38. var mouseY = (window.innerHeight + world.height) * 0.5;
  39. var mouseIsDown = false;
  40. var spaceIsDown = false;
  41. // Game properties and scoring
  42. var playing = false;
  43. var score = 0;
  44. var time = 0;
  45. var duration = 0;
  46. var difficulty = 1;
  47. var lastspawn = 0;
  48. // Game statistics
  49. var fc = 0; // Frame count
  50. var fs = 0; // Frame score
  51. var cs = 0; // Collision score
  52. // The world's velocity
  53. var velocity = { x: -1.3, y: 1 };
  54. // Performance (FPS) tracking
  55. var fps = 0;
  56. var fpsMin = 1000;
  57. var fpsMax = 0;
  58. var timeLastSecond = new Date().getTime();
  59. var frames = 0;
  60. this.init = function(){
  61. canvas = document.getElementById('world');
  62. canvasBackground = document.getElementById('background');
  63. panels = document.getElementById('panels');
  64. status = document.getElementById('status');
  65. message = document.getElementById('message');
  66. title = document.getElementById('title');
  67. startButton = document.getElementById('startButton');
  68. if (canvas && canvas.getContext) {
  69. context = canvas.getContext('2d');
  70. contextBackground = canvasBackground.getContext('2d');
  71. // Register event listeners
  72. document.addEventListener('mousemove', documentMouseMoveHandler, false);
  73. document.addEventListener('mousedown', documentMouseDownHandler, false);
  74. document.addEventListener('mouseup', documentMouseUpHandler, false);
  75. canvas.addEventListener('touchstart', documentTouchStartHandler, false);
  76. document.addEventListener('touchmove', documentTouchMoveHandler, false);
  77. document.addEventListener('touchend', documentTouchEndHandler, false);
  78. window.addEventListener('resize', windowResizeHandler, false);
  79. startButton.addEventListener('click', startButtonClickHandler, false);
  80. document.addEventListener('keydown', documentKeyDownHandler, false);
  81. document.addEventListener('keyup', documentKeyUpHandler, false);
  82. // Define our player
  83. player = new Player();
  84. // Force an initial resize to make sure the UI is sized correctly
  85. windowResizeHandler();
  86. // If we are running on mobile, certain elements need to be configured differently
  87. if( isMobile ) {
  88. status.style.width = world.width + 'px';
  89. canvas.style.border = 'none';
  90. }
  91. animate();
  92. }
  93. };
  94. function renderBackground() {
  95. var gradient = contextBackground.createRadialGradient( world.width * 0.5, world.height * 0.5, 0, world.width * 0.5, world.height * 0.5, 500 );
  96. gradient.addColorStop(0,'rgba(0, 70, 70, 1)');
  97. gradient.addColorStop(1,'rgba(0, 8, 14, 1)');
  98. contextBackground.fillStyle = gradient;
  99. contextBackground.fillRect( 0, 0, world.width, world.height );
  100. }
  101. /**
  102. * Handles click on the start button in the UI.
  103. */
  104. function startButtonClickHandler(event){
  105. if( playing == false ) {
  106. playing = true;
  107. // Reset game properties
  108. organisms = [];
  109. score = 0;
  110. difficulty = 1;
  111. // Reset game tracking properties
  112. fc = 0;
  113. fs = 0;
  114. ms = 0;
  115. cs = 0;
  116. // Reset the player data
  117. player.energy = 30;
  118. // Hide the game UI
  119. panels.style.display = 'none';
  120. status.style.display = 'block';
  121. time = new Date().getTime();
  122. }
  123. }
  124. /**
  125. * Stops the currently ongoing game and shows the
  126. * resulting data in the UI.
  127. */
  128. function gameOver() {
  129. playing = false;
  130. // Determine the duration of the game
  131. duration = new Date().getTime() - time;
  132. // Show the UI
  133. panels.style.display = 'block';
  134. // Ensure that the score is an integer
  135. score = Math.round(score);
  136. // Write the users score to the UI
  137. title.innerHTML = 'game over! (' + score + ' points)';
  138. // Update the status bar with the final score and time
  139. scoreText = 'score: <span>' + Math.round( score ) + '</span>';
  140. scoreText += ' time: <span>' + Math.round( ( ( new Date().getTime() - time ) / 1000 ) * 100 ) / 100 + 's</span>';
  141. status.innerHTML = scoreText;
  142. }
  143. function documentKeyDownHandler(event) {
  144. switch( event.keyCode ) {
  145. case 32:
  146. if( !spaceIsDown && player.energy > 15 ) {
  147. player.energy -= 4;
  148. }
  149. spaceIsDown = true;
  150. event.preventDefault();
  151. break;
  152. }
  153. }
  154. function documentKeyUpHandler(event) {
  155. switch( event.keyCode ) {
  156. case 32:
  157. spaceIsDown = false;
  158. event.preventDefault();
  159. break;
  160. }
  161. }
  162. /**
  163. * Event handler for document.onmousemove.
  164. */
  165. function documentMouseMoveHandler(event){
  166. mouseX = event.clientX - (window.innerWidth - world.width) * 0.5 - BORDER_WIDTH;
  167. mouseY = event.clientY - (window.innerHeight - world.height) * 0.5 - BORDER_WIDTH;
  168. }
  169. /**
  170. * Event handler for document.onmousedown.
  171. */
  172. function documentMouseDownHandler(event){
  173. mouseIsDown = true;
  174. }
  175. /**
  176. * Event handler for document.onmouseup.
  177. */
  178. function documentMouseUpHandler(event) {
  179. mouseIsDown = false;
  180. }
  181. /**
  182. * Event handler for document.ontouchstart.
  183. */
  184. function documentTouchStartHandler(event) {
  185. if(event.touches.length == 1) {
  186. event.preventDefault();
  187. mouseX = event.touches[0].pageX - (window.innerWidth - world.width) * 0.5;
  188. mouseY = event.touches[0].pageY - (window.innerHeight - world.height) * 0.5;
  189. mouseIsDown = true;
  190. }
  191. }
  192. /**
  193. * Event handler for document.ontouchmove.
  194. */
  195. function documentTouchMoveHandler(event) {
  196. if(event.touches.length == 1) {
  197. event.preventDefault();
  198. mouseX = event.touches[0].pageX - (window.innerWidth - world.width) * 0.5 - 60;
  199. mouseY = event.touches[0].pageY - (window.innerHeight - world.height) * 0.5 - 30;
  200. }
  201. }
  202. /**
  203. * Event handler for document.ontouchend.
  204. */
  205. function documentTouchEndHandler(event) {
  206. mouseIsDown = false;
  207. }
  208. /**
  209. * Event handler for window.onresize.
  210. */
  211. function windowResizeHandler() {
  212. // Update the game size
  213. world.width = isMobile ? window.innerWidth : DEFAULT_WIDTH;
  214. world.height = isMobile ? window.innerHeight : DEFAULT_HEIGHT;
  215. // Center the player
  216. player.position.x = world.width * 0.5;
  217. player.position.y = world.height * 0.5;
  218. // Resize the canvas
  219. canvas.width = world.width;
  220. canvas.height = world.height;
  221. canvasBackground.width = world.width;
  222. canvasBackground.height = world.height;
  223. // Determine the x/y position of the canvas
  224. var cvx = (window.innerWidth - world.width) * 0.5;
  225. var cvy = (window.innerHeight - world.height) * 0.5;
  226. // Position the canvas
  227. canvas.style.position = 'absolute';
  228. canvas.style.left = cvx + 'px';
  229. canvas.style.top = cvy + 'px';
  230. canvasBackground.style.position = 'absolute';
  231. canvasBackground.style.left = cvx + BORDER_WIDTH + 'px';
  232. canvasBackground.style.top = cvy + BORDER_WIDTH + 'px';
  233. if( isMobile ) {
  234. panels.style.left = '0px';
  235. panels.style.top = '0px';
  236. panels.style.width = '100%';
  237. panels.style.height = '100%';
  238. status.style.left = '0px';
  239. status.style.top = '0px';
  240. }
  241. else {
  242. panels.style.left = cvx + BORDER_WIDTH + 'px';
  243. panels.style.top = cvy + BORDER_WIDTH + 'px';
  244. panels.style.width = world.width + 'px';
  245. panels.style.height = world.height + 'px';
  246. status.style.left = cvx + BORDER_WIDTH + 'px';
  247. status.style.top = cvy + BORDER_WIDTH + 'px';
  248. }
  249. renderBackground();
  250. }
  251. /**
  252. * Emits a random number of particles from a set point.
  253. *
  254. * @param position The point where particles will be
  255. * emitted from
  256. * @param spread The pixel spread of the emittal
  257. */
  258. function emitParticles( position, direction, spread, seed ) {
  259. var q = seed + ( Math.random() * seed );
  260. while( --q >= 0 ) {
  261. var p = new Point();
  262. p.position.x = position.x + ( Math.sin(q) * spread );
  263. p.position.y = position.y + ( Math.cos(q) * spread );
  264. p.velocity = { x: direction.x + ( -1 + Math.random() * 2 ), y: direction.y + ( - 1 + Math.random() * 2 ) };
  265. p.alpha = 1;
  266. particles.push( p );
  267. }
  268. }
  269. /**
  270. * Called on every frame to update the game properties
  271. * and render the current state to the canvas.
  272. */
  273. function animate() {
  274. // Fetch the current time for this frame
  275. var frameTime = new Date().getTime();
  276. // Increase the frame count
  277. frames ++;
  278. // Check if a second has passed since the last time we updated the FPS
  279. if( frameTime > timeLastSecond + 1000 ) {
  280. // Establish the current, minimum and maximum FPS
  281. fps = Math.min( Math.round( ( frames * 1000 ) / ( frameTime - timeLastSecond ) ), FRAMERATE );
  282. fpsMin = Math.min( fpsMin, fps );
  283. fpsMax = Math.max( fpsMax, fps );
  284. timeLastSecond = frameTime;
  285. frames = 0;
  286. }
  287. // A factor by which the score will be scaled, depending on current FPS
  288. var scoreFactor = 0.01 + ( Math.max( Math.min( fps, FRAMERATE ), 0 ) / FRAMERATE * 0.99 );
  289. // Scales down the factor by itself
  290. scoreFactor = scoreFactor * scoreFactor;
  291. // Clear the canvas of all old pixel data
  292. context.clearRect(0,0,canvas.width,canvas.height);
  293. var i,
  294. ilen,
  295. j,
  296. jlen;
  297. // Only update game properties and draw the player if a game is active
  298. if( playing ) {
  299. // Increment the difficulty slightly
  300. difficulty += 0.0015;
  301. // Increment the score depending on difficulty
  302. score += (0.4 * difficulty) * scoreFactor;
  303. // Increase the game frame count stat
  304. fc ++;
  305. // Increase the score count stats
  306. fs += (0.4 * difficulty) * scoreFactor;
  307. //player.angle = Math.atan2( mouseY - player.position.y, mouseX - player.position.x );
  308. var targetAngle = Math.atan2( mouseY - player.position.y, mouseX - player.position.x );
  309. if( Math.abs( targetAngle - player.angle ) > Math.PI ) {
  310. player.angle = targetAngle;
  311. }
  312. player.angle += ( targetAngle - player.angle ) * 0.2;
  313. player.energyRadiusTarget = ( player.energy / 100 ) * ( player.radius * 0.8 );
  314. player.energyRadius += ( player.energyRadiusTarget - player.energyRadius ) * 0.2;
  315. player.shield = { x: player.position.x + Math.cos( player.angle ) * player.radius, y: player.position.y + Math.sin( player.angle ) * player.radius };
  316. // Shield
  317. context.beginPath();
  318. context.strokeStyle = '#648d93';
  319. context.lineWidth = 3;
  320. context.arc( player.position.x, player.position.y, player.radius, player.angle + 1.6, player.angle - 1.6, true );
  321. context.stroke();
  322. // Core
  323. context.beginPath();
  324. context.fillStyle = "#249d93";
  325. context.strokeStyle = "#3be2d4";
  326. context.lineWidth = 1.5;
  327. player.updateCore();
  328. var loopedNodes = player.coreNodes.concat();
  329. loopedNodes.push( player.coreNodes[0] );
  330. for( var i = 0; i < loopedNodes.length; i++ ) {
  331. p = loopedNodes[i];
  332. p2 = loopedNodes[i+1];
  333. p.position.x += ( (player.position.x + p.normal.x + p.offset.x) - p.position.x ) * 0.2;
  334. p.position.y += ( (player.position.y + p.normal.y + p.offset.y) - p.position.y ) * 0.2;
  335. if( i == 0 ) {
  336. // This is the first loop, so we need to start by moving into position
  337. context.moveTo( p.position.x, p.position.y );
  338. }
  339. else if( p2 ) {
  340. // Draw a curve between the current and next trail point
  341. context.quadraticCurveTo( p.position.x, p.position.y, p.position.x + ( p2.position.x - p.position.x ) / 2, p.position.y + ( p2.position.y - p.position.y ) / 2 );
  342. }
  343. }
  344. context.closePath();
  345. context.fill();
  346. context.stroke();
  347. }
  348. if( spaceIsDown && player.energy > 10 ) {
  349. player.energy -= 0.1;
  350. context.beginPath();
  351. context.strokeStyle = '#648d93';
  352. context.lineWidth = 1;
  353. context.fillStyle = 'rgba( 0, 100, 100, ' + ( player.energy / 100 ) * 0.9 + ' )';
  354. context.arc( player.position.x, player.position.y, player.radius, 0, Math.PI*2, true );
  355. context.fill();
  356. context.stroke();
  357. // play sound for the shield
  358. CoreAudio.playShield();
  359. }
  360. var enemyCount = 0;
  361. var energyCount = 0;
  362. // Go through each enemy and draw it + update its properties
  363. for( i = 0; i < organisms.length; i++ ) {
  364. p = organisms[i];
  365. p.position.x += p.velocity.x;
  366. p.position.y += p.velocity.y;
  367. p.alpha += ( 1 - p.alpha ) * 0.1;
  368. if( p.type == ORGANISM_ENEMY ) context.fillStyle = 'rgba( 255, 0, 0, ' + p.alpha + ' )';
  369. if( p.type == ORGANISM_ENERGY ) context.fillStyle = 'rgba( 0, 235, 190, ' + p.alpha + ' )';
  370. context.beginPath();
  371. context.arc(p.position.x, p.position.y, p.size/2, 0, Math.PI*2, true);
  372. context.fill();
  373. var angle = Math.atan2( p.position.y - player.position.y, p.position.x - player.position.x );
  374. if (playing) {
  375. var dist = Math.abs( angle - player.angle );
  376. if( dist > Math.PI ) {
  377. dist = ( Math.PI * 2 ) - dist;
  378. }
  379. if ( dist < 1.6 ) {
  380. if (p.distanceTo(player.position) > player.radius - 5 && p.distanceTo(player.position) < player.radius + 5) {
  381. p.dead = true;
  382. // play sound
  383. CoreAudio.organismDead();
  384. }
  385. }
  386. if (spaceIsDown && p.distanceTo(player.position) < player.radius && player.energy > 11) {
  387. p.dead = true;
  388. score += 4;
  389. }
  390. if (p.distanceTo(player.position) < player.energyRadius + (p.size * 0.5)) {
  391. if (p.type == ORGANISM_ENEMY) {
  392. player.energy -= 6;
  393. // play sound
  394. CoreAudio.energyDown();
  395. }
  396. if (p.type == ORGANISM_ENERGY) {
  397. player.energy += 8;
  398. score += 30;
  399. // play sound
  400. CoreAudio.energyUp();
  401. }
  402. player.energy = Math.max(Math.min(player.energy, 100), 0);
  403. p.dead = true;
  404. }
  405. }
  406. // If the enemy is outside of the game bounds, destroy it
  407. if( p.position.x < -p.size || p.position.x > world.width + p.size || p.position.y < -p.size || p.position.y > world.height + p.size ) {
  408. p.dead = true;
  409. }
  410. // If the enemy is dead, remove it
  411. if( p.dead ) {
  412. emitParticles( p.position, { x: (p.position.x - player.position.x) * 0.02, y: (p.position.y - player.position.y) * 0.02 }, 5, 5 );
  413. organisms.splice( i, 1 );
  414. i --;
  415. }
  416. else {
  417. if( p.type == ORGANISM_ENEMY ) enemyCount ++;
  418. if( p.type == ORGANISM_ENERGY ) energyCount ++;
  419. }
  420. }
  421. // If there are less enemies than intended for this difficulty, add another one
  422. if( enemyCount < 1 * difficulty && new Date().getTime() - lastspawn > 100 ) {
  423. organisms.push( giveLife( new Enemy() ) );
  424. lastspawn = new Date().getTime();
  425. }
  426. //
  427. if( energyCount < 1 && Math.random() > 0.996 ) {
  428. organisms.push( giveLife( new Energy() ) );
  429. }
  430. // Go through and draw all particle effects
  431. for( i = 0; i < particles.length; i++ ) {
  432. p = particles[i];
  433. // Apply velocity to the particle
  434. p.position.x += p.velocity.x;
  435. p.position.y += p.velocity.y;
  436. // Fade out
  437. p.alpha -= 0.02;
  438. // Draw the particle
  439. context.fillStyle = 'rgba(255,255,255,'+Math.max(p.alpha,0)+')';
  440. context.fillRect( p.position.x, p.position.y, 1, 1 );
  441. // If the particle is faded out to less than zero, remove it
  442. if( p.alpha <= 0 ) {
  443. particles.splice( i, 1 );
  444. }
  445. }
  446. // If the game is active, update the game status bar with score, duration and FPS
  447. if( playing ) {
  448. scoreText = 'score: <span>' + Math.round( score ) + '</span>';
  449. scoreText += ' time: <span>' + Math.round( ( ( new Date().getTime() - time ) / 1000 ) * 100 ) / 100 + 's</span>';
  450. scoreText += ' <p class="fps">fps: <span>' + Math.round( fps ) + ' ('+Math.round(Math.max(Math.min(fps/FRAMERATE, FRAMERATE), 0)*100)+'%)</span></p>';
  451. status.innerHTML = scoreText;
  452. if( player.energy === 0 ) {
  453. emitParticles( player.position, { x: 0, y: 0 }, 10, 40 );
  454. gameOver();
  455. // play sound
  456. CoreAudio.playGameOver();
  457. }
  458. }
  459. requestAnimFrame( animate );
  460. }
  461. /**
  462. *
  463. */
  464. function giveLife( organism ) {
  465. var side = Math.round( Math.random() * 3 );
  466. switch( side ) {
  467. case 0:
  468. organism.position.x = 10;
  469. organism.position.y = world.height * Math.random();
  470. break;
  471. case 1:
  472. organism.position.x = world.width * Math.random();
  473. organism.position.y = 10;
  474. break;
  475. case 2:
  476. organism.position.x = world.width - 10;
  477. organism.position.y = world.height * Math.random();
  478. break;
  479. case 3:
  480. organism.position.x = world.width * Math.random();
  481. organism.position.y = world.height - 10;
  482. break;
  483. }
  484. if( playing ) CoreAudio.playSynth( organism.position.x / world.width );
  485. organism.speed = Math.min( Math.max( Math.random(), 0.6 ), 0.75 );
  486. organism.velocity.x = ( player.position.x - organism.position.x ) * 0.006 * organism.speed;
  487. organism.velocity.y = ( player.position.y - organism.position.y ) * 0.006 * organism.speed;
  488. if( organism.type == 'enemy' ) {
  489. organism.velocity.x *= (1+(Math.random()*0.1));
  490. organism.velocity.y *= (1+(Math.random()*0.1));
  491. }
  492. organism.alpha = 0;
  493. return organism;
  494. }
  495. };
  496. function Point( x, y ) {
  497. this.position = { x: x, y: y };
  498. }
  499. Point.prototype.distanceTo = function(p) {
  500. var dx = p.x-this.position.x;
  501. var dy = p.y-this.position.y;
  502. return Math.sqrt(dx*dx + dy*dy);
  503. };
  504. Point.prototype.clonePosition = function() {
  505. return { x: this.position.x, y: this.position.y };
  506. };
  507. function Player() {
  508. this.position = { x: 0, y: 0 };
  509. this.length = 15;
  510. this.energy = 30;
  511. this.energyRadius = 0;
  512. this.energyRadiusTarget = 0;
  513. this.radius = 60;
  514. this.angle = 0;
  515. this.coreQuality = 16;
  516. this.coreNodes = [];
  517. }
  518. Player.prototype = new Point();
  519. Player.prototype.updateCore = function() {
  520. var i, j, n;
  521. if( this.coreNodes.length == 0 ) {
  522. var i, n;
  523. for (i = 0; i < this.coreQuality; i++) {
  524. n = {
  525. position: { x: this.position.x, y: this.position.y },
  526. normal: { x: 0, y: 0 },
  527. normalTarget: { x: 0, y: 0 },
  528. offset: { x: 0, y: 0 }
  529. };
  530. this.coreNodes.push( n );
  531. }
  532. }
  533. for (i = 0; i < this.coreQuality; i++) {
  534. var n = this.coreNodes[i];
  535. var angle = ( i / this.coreQuality ) * Math.PI * 2;
  536. n.normal.x = Math.cos( angle ) * this.energyRadius;
  537. n.normal.y = Math.sin( angle ) * this.energyRadius;
  538. n.offset.x = Math.random() * 5;
  539. n.offset.y = Math.random() * 5;
  540. }
  541. };
  542. function Enemy() {
  543. this.position = { x: 0, y: 0 };
  544. this.velocity = { x: 0, y: 0 };
  545. this.size = 6 + ( Math.random() * 4 );
  546. this.speed = 1;
  547. this.type = 'enemy';
  548. }
  549. Enemy.prototype = new Point();
  550. function Energy() {
  551. this.position = { x: 0, y: 0 };
  552. this.velocity = { x: 0, y: 0 };
  553. this.size = 10 + ( Math.random() * 6 );
  554. this.speed = 1;
  555. this.type = 'energy';
  556. }
  557. Energy.prototype = new Point();
  558. // shim with setTimeout fallback from http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  559. window.requestAnimFrame = (function(){
  560. return window.requestAnimationFrame ||
  561. window.webkitRequestAnimationFrame ||
  562. window.mozRequestAnimationFrame ||
  563. window.oRequestAnimationFrame ||
  564. window.msRequestAnimationFrame ||
  565. function(/* function */ callback, /* DOMElement */ element){
  566. window.setTimeout(callback, 1000 / 60);
  567. };
  568. })();
  569. Core.init();