Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 600 lines (533 sloc) 22.762 kb
47f691ac »
2012-08-07 compatability fixes for IE
1 <!DOCTYPE html> <!-- this !DOCTYPE declaration identifies this file as an HTML5 file.
2 It's not strictly necessary in most cases, but it does keep IE from pooping the bed. -->
3
02cd433a »
2012-08-06 initial upload
4
5 <!-- every HTML document starts with the HTML tag -->
6 <html>
7
8 <!-- the HEAD is where we put everything that is NOT actual, visible content. -->
9 <head>
10
11 <!-- this META tag is used by the browser to know how to translate the raw byte data
12 of the file into character data that it can interpret. This isn't strictly necessary,
13 but it's nice to include. -->
14 <meta charset="UTF-8">
15
16 <!-- the following META tags define some features for iPhone and iPad -->
17 <meta name="apple-mobile-web-app-capable" content="yes">
18 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
19
20 <!-- this META tag is used by mobile browsers to figure out how to display
21 the page. We do this because we don't want people to resize it. -->
47f691ac »
2012-08-07 compatability fixes for IE
22 <meta name="viewport" content="user-scalable=no, width=800">
02cd433a »
2012-08-06 initial upload
23 <!-- this will show up in the browser window's title bar -->
24 <title>Ping Pong</title>
25
26 <!-- normally, one would put the STYLE declaration into a separate file, but
27 in this case, it's convenient to keep everything in the same file. Cascading
28 Style Sheets (CSS) are used to setup the shape and colors of things. -->
29 <style type="text/css">
30 /* different browsers vary in how much margin or
31 padding they apply to the root level document,
32 so we force everyone to have the same amount. */
33 body
34 {
47f691ac »
2012-08-07 compatability fixes for IE
35 background-color: #000000;
02cd433a »
2012-08-06 initial upload
36 margin: 0;
37 padding: 0;
38 }
39
40 /* It's not typical to include a rule for DIV tags.
41 In this case, we have a very limited example, so
42 specifying a rule for the DIV tag is expedient. */
43 div
44 {
45 position: absolute;
46 margin: 0;
47 padding: 0;
48 background-color: #00ff00;
49 }
50
51 /* There are two different score indicators, so the
52 rule that affects their font appearance will be
53 applied as a class. */
54 .score
55 {
56 top: 30px;
57 color: #00ff00;
58 background-color: transparent;
59 font-family: fixedsys, Courier, "Courier New" , fixed;
60 font-weight: bold;
61 font-size: 36pt;
62 }
63
64 /* and then the individual score counters are positioned
65 seperately */
66 #score1
67 {
68 left: 325px;
69 }
70
71 #score2
72 {
73 left: 445px;
74 }
75
76 /* This is kind of the "background" of the game, as well
77 as a container that can be used to reposition it
78 essentially anywhere. */
79 #board
80 {
81 top: 0px;
82 left: 0px;
83 width: 800px;
84 height: 400px;
85 border-top: solid 10px #00ff00;
86 border-bottom: solid 10px #00ff00;
87 background-color: #000000;
88 }
89
90 /* This is just a visual reference. It doesn't actually
91 affect the game play at all. */
92 #divider
93 {
94 top: 0px;
95 left: 395px;
96 width: 10px;
97 height: 400px;
98 }
99
100 /* This is kind of cheap, but sometimes you just want something
101 done. This is all that suffices for a "title" screen in the
102 game. It gives the user a chance to acclimate to the layout
103 and start on their own time. */
104 button#start
105 {
106 position: absolute;
107 top: 175px;
108 left: 300px;
109 width: 200px;
110 height: 50px;
111 }
112
113 /* Never forget that you need to give quite a bit of feedback
114 to the user. This setups the block of text that will tell
115 the user what is going on. */
116 #status
117 {
118 top: 170px;
119 left: 95px;
120 width: 600px;
121 padding: 10px;
122 text-align: center;
123 background-color: black;
124 display: none;
125 }
126 </style>
127
128 <!-- again, the SCRIPT declaration would typically appear in its own file,
129 but it is more convenient today to keep them in one. -->
130 <script type="text/javascript">
131 /* JavaScript is an functional and object oriented language. To
132 define "classes", you define a function that saves attributes
133 to itself. */
134
135 /* by making these specific values variables, we can make the game
136 more customizable. */
137 var PLAY_WIDTH = 800;
138 var PLAY_HEIGHT = 400;
139 var LINE_WIDTH = 10;
140 var PADDLE_LENGTH = 100;
141
142 /* This is a convenience function to cover a fairly common task.
143 Basically, this is just creating the same <DIV> tag that are
144 declared below in the HTML, but doing it through JavaScript. */
145 function makeRect(x, y, width, height, bgcolor)
146 {
147 /* This is a function provided by the browser to create HTML elements on-the-fly.
148 The element is not automatically added to the page. */
149 var elem = document.createElement("div");
150
151 /* These are all CSS attributes for the element that we are setting
152 through JavaScript. */
153 var s = elem.style;
154 s.position = "absolute";
155 s.padding = "0";
156 s.backgroundColor = bgcolor;
157 s.top = y + "px";
158 s.left = x + "px";
159 s.width = width + "px";
160 s.height = height + "px";
161
162 /* This makeRect() function does not do anything to the page itself,
163 it merely creates the rectangle and gives it back to whomever
164 called it. */
165 return elem;
166 }
167
47f691ac »
2012-08-07 compatability fixes for IE
168
169 /* a little something for IE <= 8's benefit */
170 if (!document.addEventListener)
171 {
172 document.addEventListener = function (eventName, func)
173 {
174 eventName = "on" + eventName;
175 if (this[eventName])
176 {
177 var temp = this[eventName];
178 this[eventName] = function ()
179 {
180 console.log(event);
181 temp(event);
182 func(event);
183 };
184 }
185 else
186 {
187 this[eventName] = function () { func(event) };
188 }
189 };
190 }
191
02cd433a »
2012-08-06 initial upload
192 /* This class represents the player's paddle and score. */
193 function Player(scoreDisplay, x, y, width, height)
194 {
195 this.x = x;
196 this.y = y;
197 this.startY = y;
198 this.width = width;
199 this.height = height;
200 this.score = 0;
201 this.scoreDisplay = scoreDisplay;
202 this.elem = makeRect(this.x, this.y, this.width, this.height, "#00ff00");
203 document.getElementById("board").appendChild(this.elem);
204 }
205
206 /* the following functions are called methods. Methods are functions that
207 apply to classes. Because we have to set them as properties of the class,
208 we have to use an alternative form of the function declaration.*/
209
210 /* The reset() method is used after a Game Over to put everything back to its
211 starting position */
212 Player.prototype.reset = function ()
213 {
214 this.moveTo(this.startY);
215 this.score = 0;
216 this.scoreDisplay.innerHTML = this.score;
217 };
218
219 /* The moveTo() method is used to move the paddle, check to make it stay inside
220 the bounds of play, and update the display of the paddle on the screen */
221 Player.prototype.moveTo = function (y)
222 {
223 this.y = y;
224 if (this.y > PLAY_HEIGHT - PADDLE_LENGTH)
225 {
226 this.y = PLAY_HEIGHT - PADDLE_LENGTH;
227 }
228 else if (this.y < 0)
229 {
230 this.y = 0;
231 }
232 this.elem.style.top = Math.floor(this.y) + "px";
233 };
234
235 /* The scored() method is used to increase a player's score and update the
236 display of the score. */
237 Player.prototype.scored = function ()
238 {
239 this.score++;
240 this.scoreDisplay.innerHTML = this.score;
241 };
242
243 /* The AI() method is the Artificial Intelligience for paddles. This implementation
244 is pretty stupid and easy to beat, but offers a fairly reasonable behavior that is
245 nice to play against for such a simple game. */
246 Player.prototype.AI = function (ball)
247 {
248 /* the AI player is only going to play when the ball is moving towards it.
249 This means that the AI is specifically only coded for the paddle on the right
250 side of the play area */
251 if (ball.dx > 0)
252 {
253 if (ball.y > this.y + this.height - LINE_WIDTH)
254 {
255 // the ball has moved past the bottom of the paddle
256 this.moveTo(this.y + 1);
257 }
258 else if (ball.y < this.y)
259 {
260 // the ball has moved past the top of the paddle
261 this.moveTo(this.y - 1);
262 }
263 }
264 };
265
47f691ac »
2012-08-07 compatability fixes for IE
266
02cd433a »
2012-08-06 initial upload
267 /* The intersect() method checks to see if the rectangle for the ball
268 overlaps the rectangle for the paddle in any way. It returns a TRUE/FALSE
269 value that can be used to direct the calling function. */
270 Player.prototype.intersect = function (ball)
271 {
272 return ball.x + ball.width >= this.x
273 && ball.x <= this.x + this.width
274 && ball.y + ball.height >= this.y
275 && ball.y < this.y + this.height;
276 };
277
278 /* The bounce() methods checks to see if the ball hit the paddle (using
279 the intersect() method), then reflects the ball away, adding some english
280 to the ball for strategic players to exploit. */
281 Player.prototype.bounce = function (ball)
282 {
283 if (this.intersect(ball))
284 {
285 /* Increase the speed of the ball slightly */
286 ball.dx *= -1.1;
287
288 /* determine how far the middle of the ball is from the middle
289 of the paddle. */
290 var dy = (this.y + this.height / 2) - (ball.y + ball.height / 2);
291
292 /* add some english to the ball as it bounces away, proportional
293 to how far from the center of the paddle the ball is. The value
294 of 333 has no inherent meaning. I picked it because it seemed to
295 work well. This is what is usually called a "fudge factor". */
296 ball.dy -= dy / 333;
297
298 /* tell the calling function a collision occured */
299 return true;
300 }
301
302 /* or, tell the calling function that no collision occured */
303 return false;
304 }
305
306 /* This class represents the ball's position. */
307 function Ball(width, height)
308 {
309 //this keeps the ball off the screen so it doesn't appear in a weird place.
310 this.x = -1000;
311 this.y = 0;
312 this.dx = 0;
313 this.dy = 0;
314 this.width = width;
315 this.height = height;
316 this.elem = makeRect(this.x, this.y, this.width, this.height, "#00ff00");
317 document.getElementById("board").appendChild(this.elem);
318 }
319
320 /* The drop() function puts the ball "in play". It takes a direction and a
321 max-speed for the ball, then figures out a semi-random direction in which to
322 launch the ball. Initially, the ball is setup way off-screen so it doesn't appear
323 before the game has started. */
324 Ball.prototype.drop = function (direction, speed)
325 {
326 //get the ball out of view before the start of play
47f691ac »
2012-08-07 compatability fixes for IE
327 this.x = -100;
02cd433a »
2012-08-06 initial upload
328 this.y = 0;
329 this.display();
330
331 //Putting the ball in the center of the play area
332 this.x = (PLAY_WIDTH - LINE_WIDTH) / 2;
333 this.y = (PLAY_HEIGHT - LINE_WIDTH) / 2;
334
335 /* Another "fudge factor" for speed. Ensures a minimum speed (0.2 pixels
336 per millisecond) as well as a maximum speed (0.2 + 0.3 = 0.5 pixels per
337 millisecond). The speed is based on the current combined score of the
338 two players, and as the game is to 5, the highest score before a game-over
339 is 4 to 4, so the highest speed is 8. */
340 var vel = 0.2 + 0.3 * speed / 8;
341
342 /* Using a random number generator, picks an angle at which to launch the
343 ball. It's will be any angle in a 90 degree wedge pointed in the direction
344 of one of the paddles. */
345 var angle = (Math.random() * 2 - 1) * Math.PI / 4;
346
347 /* sets the initial velocity of the ball */
348 this.dx = direction * Math.cos(angle) * vel;
349 this.dy = Math.sin(angle) * vel;
350 };
351
352 /* The display() method puts the ball's representing DIV tag where it should
353 be on the display area. This is done separately because we may need to update
354 the location of the ball many times before showing its location on screen. */
355 Ball.prototype.display = function ()
356 {
357 this.elem.style.top = Math.floor(this.y) + "px";
358 this.elem.style.left = Math.floor(this.x) + "px";
359 };
360
361 /* The advance() method moves the ball forward by its velocity */
362 Ball.prototype.advance = function ()
363 {
364 this.x += this.dx;
365 this.y += this.dy;
366 };
367
368 /* The bounced() method checks to see if the ball has bounced off of anything,
369 either the top/bottom walls of the play field or one of the players' paddles. */
370 Ball.prototype.bounced = function (p1, p2, maxY)
371 {
372 // check to see if the ball hit the top wall
373 if (this.y < 0 || this.y >= maxY)
374 {
375 this.dy *= -1;
376 }
377
378 // check to see if the ball hit either of the paddles.
379 return p1.bounce(this) || p2.bounce(this);
380 };
381
382 /* These variables are used to keep track of things that we
383 need to play the game. */
384 var p1, p2, ball, timer, lastFrame, startButton, statusBox, state;
385 window.onload = function ()
386 {
387 // need to be able to show/hide the start button
388 startButton = document.getElementById("start");
389 // need to be able to show/hide the status display
390 statusBox = document.getElementById("status");
391
392 // This should be pretty self-explanatory
393 ball = new Ball(LINE_WIDTH, LINE_WIDTH);
394
395 // making the paddles for each of the players
396 p1 = new Player(
397 document.getElementById("score1"), // this player's score box
398 LINE_WIDTH, // the location of the paddle is spaced enough to give a full ball-width behind it
399 (PLAY_HEIGHT - PADDLE_LENGTH) / 2, // vertically center the paddle on the play area
400 LINE_WIDTH, // the paddle's width is the same size as all of the other lines
401 PADDLE_LENGTH); // standardized height
402
403 // exactly the same as the other paddle, except on the opposite side of the screen.
404 p2 = new Player(
405 document.getElementById("score2"),
406 PLAY_WIDTH - LINE_WIDTH * 2, // the left edge of the paddle is a ball's width and a paddle's width away from the right edge of the board
407 (PLAY_HEIGHT - PADDLE_LENGTH) / 2,
408 LINE_WIDTH,
409 PADDLE_LENGTH);
410 };
411
412
413 /* This is an "event". It occurs when something happens. In this case, when the
414 user moves their mouse. We use it to move the user's paddle. */
47f691ac »
2012-08-07 compatability fixes for IE
415
02cd433a »
2012-08-06 initial upload
416 function movePlayer1(y)
417 {
418 p1.moveTo(y - PADDLE_LENGTH / 2);
419 }
420
421 if(navigator.userAgent.indexOf("iPad") != -1
422 || navigator.userAgent.indexOf("iPod") != -1
423 || navigator.userAgent.indexOf("iPhone") != -1
424 || navigator.userAgent.indexOf("Android") != -1)
425 {
426 console.log("Is Mobile OS");
47f691ac »
2012-08-07 compatability fixes for IE
427 document.addEventListener("touchmove", function (evt)
428 {
429 if (evt.touches.length > 0)
430 {
431 movePlayer1(evt.touches[0].pageY);
432 }
433 evt.preventDefault();
434 });
02cd433a »
2012-08-06 initial upload
435 }
436 else
437 {
438 console.log("Is Desktop OS");
47f691ac »
2012-08-07 compatability fixes for IE
439 document.addEventListener("mousemove", function (evt)
02cd433a »
2012-08-06 initial upload
440 {
441 movePlayer1(evt.clientY);
47f691ac »
2012-08-07 compatability fixes for IE
442 });
02cd433a »
2012-08-06 initial upload
443 }
444
445
446 /* the following functions are all game-state management functions.
447 The game goes through multiple states: title screen, play, between-points,
448 and game-over. These functions handle one of each. */
449
450 /* we're doing literally nothing for the title screen. A separate button
451 on the page controls this */
452 function titleScreen(delta)
453 {
454 //not going to do anything right now
455 }
456
457 /* The time before the ball goes live is used to prepare the user.
458 The delta parameter to the function tells the function how long it
459 has been since the last major update. */
460 function prePlay(delta)
461 {
462 if (delta < 1000) // Displayed during the first second
463 statusBox.innerHTML = "Ready...";
464 else if (delta < 2000) // Displayed during the second second
465 statusBox.innerHTML = "Set...";
466 else if (delta < 3000) // Displayed during the third second
467 statusBox.innerHTML = "GO!";
468 else
469 {
470 statusBox.style.display = "none"; // hide the status box we were just using
471 state = updateGame; // change the state of the game to live-action
472 return true; // tell the calling function that this function says its okay to update the timer
473 }
474 return false;
475 }
476
477 /* This is "The Game". This is were all of the logic for the game is managed. */
478 function updateGame(delta)
479 {
480 /* to update the location of the game, we're going to simulate it's position at
481 every millisecond since the last update. */
482 for (var i = 0; i < delta && state == updateGame; ++i)
483 {
484 // move the ball
485 ball.advance();
486
487 // check to see what happened to the ball
488 if (!ball.bounced(p1, p2, PLAY_HEIGHT - LINE_WIDTH))
489 {
490 var scoringPlayer = null;
491 /* if the ball didn't hit anything, did it go past
492 any of the paddles? */
493 if (ball.x < p1.x)
494 {
495 // slipping past paddle 1 means player 2 scored
496 scoringPlayer = p2;
497 }
498 else if (ball.x > p2.x)
499 {
500 // slipping past paddle 1 means player 2 scored
501 scoringPlayer = p1;
502 }
503
504 // if one of the players scored
505 if(scoringPlayer != null)
506 {
507 // increase their score
508 scoringPlayer.scored();
509 //redrop the ball
510 ball.drop(scoringPlayer == p1 ? 1 : -1, p1.score + p2.score);
511 //show the status box for the next game state
512 statusBox.style.display = "block";
513 if (scoringPlayer.score < 5)
514 {
515 /* we're still playing if the person who just scored hasn't hit
516 the max score. */
517 state = prePlay;
518 }
519 else
520 {
521 state = gameOver;
522 }
523 }
524 }
525 }
526
527 if (state == updateGame)
528 {
529 /* If we're still playing and haven't gone to the game-over or inter-point state,
530 then we'll run the P2 AI based on the ball's new location */
47f691ac »
2012-08-07 compatability fixes for IE
531 for (var i = 0; i < delta; i += AI_STEP)
02cd433a »
2012-08-06 initial upload
532 {
533 p2.AI(ball);
534 }
535 ball.display();
536 }
537
538 return true;
539 }
47f691ac »
2012-08-07 compatability fixes for IE
540
541 var AI_STEP = 5;
02cd433a »
2012-08-06 initial upload
542
543 /* This is really simple, mostly just spends a few of seconds displaying the Game Over
544 text before resetting the game to the title screen */
545 function gameOver(delta)
546 {
547 if (delta < 3000)
548 {
549 statusBox.innerHTML = "Game Over";
550 }
551 else
552 {
553 clearInterval(timer);
554 statusBox.style.display = "none";
555 startButton.style.display = "block";
556 p1.reset();
557 p2.reset();
558 state = titleScreen;
559 return true;
560 }
561 return false;
562 }
563
564
565 /* this function is the "time keeper" of the game. We have to know how much time
566 it took to update the screen each time we've shown something, so we know how far
567 to move the ball next time. */
568 function timerTick()
569 {
570 var currentFrame = new Date().getTime();
571 var delta = currentFrame - lastFrame;
572 if (state(delta))
573 {
574 lastFrame = currentFrame;
575 }
576 }
577
578 /* this function is called when the start button is clicked. It initializes the game
579 and starts the timer working. */
580 function start()
581 {
582 startButton.style.display = "none";
583 statusBox.style.display = "block";
584 lastFrame = new Date().getTime();
585 ball.drop(1, 0);
586 state = prePlay;
47f691ac »
2012-08-07 compatability fixes for IE
587 timer = setInterval(timerTick, 33); //roughly 30 FPS (1000 / 30) = 33
02cd433a »
2012-08-06 initial upload
588 }
589 </script>
590 </head>
591 <body>
592 <div id="board">
593 <div id="divider"></div>
594 <div id="score1" class="score">0</div>
595 <div id="score2" class="score">0</div>
596 <div id="status" class="score">Ready</div>
597 <button onclick="start()" id="start">Start</button>
598 </div>
599 </body>
600 </html>
Something went wrong with that request. Please try again.