1. /**
  2.  * PONG
  3.  */
  4.  
  5. var Pong = (function(){
  6.  
  7. /**
  8.   * Setup default settings
  9.   */
  10. var defaults = {
  11. width: 500,
  12. height: 350,
  13. ballSpeed: 6,
  14. paddleSpeed: 8,
  15. ball: {
  16. width: 20,
  17. height: 20,
  18. color: 'rgb(0,0,0)'
  19. },
  20. topPaddle: {
  21. width: 80,
  22. height: 20,
  23. color: 'rgb(255,0,0)',
  24. left: "a",
  25. right: "s"
  26. },
  27. bottomPaddle: {
  28. width: 80,
  29. height: 20,
  30. color: 'rgb(0,0,255)',
  31. left: "left",
  32. right: "right"
  33. }
  34. };
  35.  
  36. /**
  37.   * Constructor for Pong, i.e. a new game of Pong
  38.   */
  39. function Pong(config) {
  40.  
  41. this.config = config = merge( defaults, config );
  42.  
  43. this.canvas = document.createElement('canvas');
  44. this.height = config.height;
  45. this.width = config.width;
  46.  
  47. this.paddleSpeed = config.paddleSpeed;
  48. this.ballSpeed = config.ballSpeed;
  49.  
  50. this.context = this.canvas.getContext('2d');
  51.  
  52. this.canvas.height = this.height;
  53. this.canvas.width = this.width;
  54.  
  55. this.newGame();
  56.  
  57. }
  58.  
  59. /**
  60.   * keyIsDown method, used in Paddle instances to determine
  61.   * whether a key is down at any time
  62.   */
  63. Pong.keyIsDown = (function(){
  64.  
  65. var keys = {
  66. 37: "left",
  67. 39: "right",
  68. 65: "a",
  69. 83: "s"
  70. };
  71.  
  72. var down = {};
  73.  
  74. document.onkeydown = function(e) {
  75. var key = (e || window.event).keyCode;
  76. down[keys[key]] = true;
  77. };
  78.  
  79. document.onkeyup = function(e) {
  80. var key = (e || window.event).keyCode;
  81. down[keys[key]] = false;
  82. };
  83.  
  84. return function(key){
  85. return !!down[key];
  86. };
  87.  
  88. })();
  89.  
  90. /**
  91.   * Paddle constructor, prepares Paddle instances, nothing special
  92.   */
  93. function Paddle(pong, config, position){
  94.  
  95. this.config = config;
  96. this.width = config.width;
  97. this.height = config.height;
  98. this.x = position.x;
  99. this.y = position.y;
  100. this.pong = pong;
  101.  
  102. }
  103.  
  104. /**
  105.   * Ball constructor, prepares Ball instances,
  106.   * determines initial deltaX and deltaY fields
  107.   * dependent on specified ballSpeed. E.g. if we want
  108.   * a speed of 5, then we must make sure that:
  109.   * Math.abs(deltaX) + Math.abs(deltaY) === 5
  110.   */
  111. function Ball(pong, config, position){
  112.  
  113. this.config = config;
  114. this.width = config.width;
  115. this.height = config.height;
  116.  
  117. this.deltaY = Math.floor(Math.random() * pong.ballSpeed) + 1;
  118. this.deltaX = pong.ballSpeed - this.deltaY;
  119.  
  120. // Half the time, we want to reverse deltaY
  121. // (making the ball begin in a random direction)
  122. if ( Math.random() > 0.5 ) {
  123. this.deltaY = -this.deltaY;
  124. }
  125.  
  126. // Half the time, we want to reverse deltaX
  127. // (making the ball begin in a random direction)
  128. if ( Math.random() > 0.5 ) {
  129. this.deltaX = -this.deltaX;
  130. }
  131.  
  132. this.x = position.x;
  133. this.y = position.y;
  134. this.pong = pong;
  135.  
  136. this.delay = 50;
  137.  
  138. }
  139.  
  140. /**
  141.   * merge - helper function to combine objects, giving
  142.   * the "target" precedence. Used to add to prototypes.
  143.   */
  144. function merge( target, source ) {
  145.  
  146. if ( typeof target !== 'object' ) {
  147. target = {};
  148. }
  149.  
  150. for (var property in source) {
  151.  
  152. if ( source.hasOwnProperty(property) ) {
  153.  
  154. var sourceProperty = source[ property ];
  155.  
  156. if ( typeof sourceProperty === 'object' ) {
  157. target[ property ] = merge( target[ property ], sourceProperty );
  158. continue;
  159. }
  160.  
  161. target[ property ] = sourceProperty;
  162.  
  163. }
  164.  
  165. }
  166.  
  167. for (var a = 2, l = arguments.length; a < l; a++) {
  168. merge(target, arguments[a]);
  169. }
  170.  
  171. return target;
  172.  
  173. }
  174.  
  175. /**
  176.   * The setLocation method is the same for the Ball and
  177.   * Paddle classes, for now, we're just drawing rectangles!
  178.   */
  179. Paddle.prototype.setLocation = Ball.prototype.setLocation = function( x, y ) {
  180. this.pong.context.fillStyle = this.config.color;
  181. this.pong.context.fillRect(
  182. x - this.width / 2,
  183. y - this.height / 2,
  184. this.width,
  185. this.height
  186. );
  187. };
  188.  
  189. merge( Pong.prototype, {
  190.  
  191. /**
  192.   * A new game, initialises a top and bottom paddle, and
  193.   * calls newRound
  194.   */
  195. newGame: function() {
  196.  
  197. this.topPaddle = new Paddle(this, this.config.topPaddle, {
  198. x: this.width / 2,
  199. y: this.config.ball.height + this.config.topPaddle.height/2
  200. });
  201.  
  202. this.bottomPaddle = new Paddle(this, this.config.bottomPaddle, {
  203. x: this.width / 2,
  204. y: this.height - this.config.ball.height - this.config.bottomPaddle.height/2
  205. });
  206.  
  207. this.newRound();
  208.  
  209. },
  210.  
  211. /**
  212.   * newRound initalises a new Ball!
  213.   */
  214. newRound: function() {
  215.  
  216. this.ball = new Ball(this, this.config.ball, {
  217. x: this.width / 2,
  218. y: this.height / 2
  219. });
  220.  
  221. },
  222.  
  223. /**
  224.   * Starting a new game involves initialising an
  225.   * interval which will run every 20 milliseconds
  226.   */
  227. start: function() {
  228.  
  229. var pong = this;
  230.  
  231. pong.interval = setInterval(function(){
  232. pong.draw();
  233. }, 20);
  234.  
  235. // Return this for chainability
  236. return this;
  237.  
  238. },
  239.  
  240. /**
  241.   * draw, called every few milliseconds to draw each
  242.   * object to the canvas
  243.   */
  244. draw: function() {
  245. this.context.clearRect( 0, 0, this.width, this.height);
  246. this.topPaddle.draw();
  247. this.bottomPaddle.draw();
  248. this.ball.draw();
  249. }
  250.  
  251. });
  252.  
  253. merge( Paddle.prototype, {
  254.  
  255. /**
  256.   * If this is the TOP paddle
  257.   */
  258. isTop: function() {
  259. return this === this.pong.topPaddle;
  260. },
  261.  
  262. /**
  263.   * intersectsBall, determines whether a ball is currently
  264.   * touching the paddle
  265.   */
  266. intersectsBall: function() {
  267.  
  268. var bX = this.pong.ball.x,
  269. bY = this.pong.ball.y,
  270. bW = this.pong.ball.width,
  271. bH = this.pong.ball.height;
  272.  
  273. return (
  274. this.isTop() ?
  275. (bY - bH/2 <= this.y + this.height/2 && bY + bH/2 > this.y + this.height/2) :
  276. (bY + bH/2 >= this.y - this.height/2 && bY - bH/2 < this.y - this.height/2)
  277. ) &&
  278. bX + bW/2 >= this.x - this.width/2 &&
  279. bX - bW/2 <= this.x + this.width/2;
  280.  
  281. },
  282.  
  283. /**
  284.   * Prepares a new frame, by taking into account the current
  285.   * position of the paddle and the ball. setLocation is called
  286.   * at the end to actually draw to the canvas!
  287.   */
  288. draw: function() {
  289.  
  290. var config = this.config,
  291. pong = this.pong,
  292. ball = pong.ball,
  293. ballSpeed = pong.ballSpeed,
  294.  
  295. xFromPaddleCenter,
  296. newDeltaX,
  297. newDeltaY;
  298.  
  299. if ( Pong.keyIsDown(config.left) && !this.isAtLeftWall() ) {
  300. this.x -= pong.paddleSpeed;
  301. }
  302.  
  303. if ( Pong.keyIsDown(config.right) && !this.isAtRightWall() ) {
  304. this.x += pong.paddleSpeed;
  305. }
  306.  
  307. if ( this.intersectsBall() ) {
  308.  
  309. xFromPaddleCenter = (ball.x - this.x) / (this.width / 2);
  310. xFromPaddleCenter = xFromPaddleCenter > 0 ? Math.min(1, xFromPaddleCenter) : Math.max(-1, xFromPaddleCenter);
  311.  
  312. if ( Math.abs(xFromPaddleCenter) > 0.5 ) {
  313. ballSpeed += ballSpeed * Math.abs(xFromPaddleCenter);
  314. }
  315.  
  316. newDeltaX = Math.min( ballSpeed - 2, xFromPaddleCenter * (ballSpeed - 2) );
  317. newDeltaY = ballSpeed - Math.abs(newDeltaX);
  318.  
  319. ball.deltaY = this.isTop() ? Math.abs(newDeltaY) : -Math.abs(newDeltaY);
  320. ball.deltaX = newDeltaX;
  321.  
  322. }
  323.  
  324. this.setLocation( this.x , this.y );
  325.  
  326. },
  327.  
  328. /**
  329.   * If the paddle is currently touching the right wall
  330.   */
  331. isAtRightWall: function() {
  332. return this.x + this.width/2 >= this.pong.width;
  333. },
  334.  
  335. /**
  336.   * If the paddle is currently touching the left wall
  337.   */
  338. isAtLeftWall: function() {
  339. return this.x - this.width/2 <= 0;
  340. }
  341.  
  342. });
  343.  
  344. merge( Ball.prototype, {
  345.  
  346. /**
  347.   * If the ball is currently touching a wall
  348.   */
  349. isAtWall: function() {
  350. return this.x + this.width/2 >= this.pong.width || this.x - this.width/2 <= 0;
  351. },
  352.  
  353. /**
  354.   * If the ball is currently at the top
  355.   */
  356. isAtTop: function() {
  357. return this.y - this.height/2 <= 0;
  358. },
  359.  
  360. /**
  361.   * If the paddle is currently at the bottom
  362.   */
  363. isAtBottom: function() {
  364. return this.y + this.height/2 >= this.pong.height;
  365. },
  366.  
  367. /**
  368.   * Simply continues the progression of the ball, by
  369.   * setting the location to the prepared x/y values
  370.   */
  371. persist: function() {
  372. this.setLocation(
  373. this.x,
  374. this.y
  375. );
  376. },
  377.  
  378. /**
  379.   * Takes care of the inital delay on each new round
  380.   */
  381. delaying: function() {
  382. return --this.delay > 0;
  383. },
  384.  
  385. /**
  386.   * Prepares the ball to be drawn to the canvas,
  387.   * to bounce the ball off the walls, the deltaX
  388.   * field is simply inverted (+5 becomes -5).
  389.   */
  390. draw: function() {
  391.  
  392. if ( this.delaying() ) {
  393. this.persist();
  394. return;
  395. }
  396.  
  397. if ( this.isAtWall() ) {
  398. this.deltaX = -this.deltaX;
  399. }
  400.  
  401. if ( this.isAtBottom() || this.isAtTop() ) {
  402. this.pong.newRound();
  403. }
  404.  
  405. this.x = this.x + this.deltaX;
  406. this.y = this.y + this.deltaY;
  407.  
  408. this.persist();
  409.  
  410. }
  411.  
  412. });
  413.  
  414. return Pong;
  415.  
  416. })();

ALL COPYRIGHT © James Padolsey unless otherwise specified
Go back to the top