Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/apps.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/pong
diff options
context:
space:
mode:
authorFrank Karlitschek <frank@owncloud.org>2014-09-02 17:39:00 +0400
committerFrank Karlitschek <frank@owncloud.org>2014-09-02 17:39:00 +0400
commita262d35c3f46a511a3e4937f34352018f98335a5 (patch)
tree736c885ae2797e98b10ba74501c79ca783d03aca /pong
parentdc91843a0cf4c87fdb430f9fd4bbc6ae53e4337b (diff)
inital commit of the pong app
Diffstat (limited to 'pong')
-rwxr-xr-xpong/LICENSE19
-rwxr-xr-xpong/README.md15
-rwxr-xr-xpong/appinfo/app.php25
-rwxr-xr-xpong/appinfo/info.xml10
-rwxr-xr-xpong/appinfo/version1
-rwxr-xr-xpong/css/pong.css44
-rwxr-xr-xpong/img/pong.pngbin0 -> 195 bytes
-rwxr-xr-xpong/img/press1.pngbin0 -> 2066 bytes
-rwxr-xr-xpong/img/press2.pngbin0 -> 1928 bytes
-rwxr-xr-xpong/img/winner.pngbin0 -> 768 bytes
-rwxr-xr-xpong/index.php35
-rwxr-xr-xpong/js/game.js300
-rwxr-xr-xpong/js/pong.js641
-rwxr-xr-xpong/js/script.js23
-rwxr-xr-xpong/sounds/goal.wavbin0 -> 10690 bytes
-rwxr-xr-xpong/sounds/ping.wavbin0 -> 4700 bytes
-rwxr-xr-xpong/sounds/pong.wavbin0 -> 2220 bytes
-rwxr-xr-xpong/sounds/wall.wavbin0 -> 1068 bytes
-rwxr-xr-xpong/templates/main.php33
19 files changed, 1146 insertions, 0 deletions
diff --git a/pong/LICENSE b/pong/LICENSE
new file mode 100755
index 000000000..45aed8553
--- /dev/null
+++ b/pong/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011, 2012, 2013 Jake Gordon and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pong/README.md b/pong/README.md
new file mode 100755
index 000000000..206abdb7b
--- /dev/null
+++ b/pong/README.md
@@ -0,0 +1,15 @@
+Canvas Pong
+===========
+
+This is a small experiment to implement a version of the classic Pong game in an HTML <canvas>.
+
+ * You can find the [game here](http://codeincomplete.com/posts/2011/5/14/javascript_pong/demo.html)
+ * You can find out [how it works](http://codeincomplete.com/posts/2011/5/14/javascript_pong/index.html)
+
+You can also play this game in the [Chrome Web Store](https://chrome.google.com/webstore/detail/omimkinlomnncbmnceacpkmlbfaapojj?hl=en-US)
+
+
+NOTES
+=====
+
+ * No support for mobile devices (need to handle touch events instead of key input)
diff --git a/pong/appinfo/app.php b/pong/appinfo/app.php
new file mode 100755
index 000000000..33d9d43a0
--- /dev/null
+++ b/pong/appinfo/app.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * ownCloud - pong app
+ *
+ * @author Frank Karlitschek
+ * @copyright 2014 Frank Karlitschek frank@owncloud.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+OCP\App::addNavigationEntry(
+ array('id' => 'pong', 'order' => 80, 'href' => OCP\Util::linkTo('pong', 'index.php') , 'icon' => OCP\Util::imagePath('pong', 'pong.png'), 'name' => 'Pong'));
diff --git a/pong/appinfo/info.xml b/pong/appinfo/info.xml
new file mode 100755
index 000000000..672032f73
--- /dev/null
+++ b/pong/appinfo/info.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<info>
+ <id>pong</id>
+ <name>Pong</name>
+ <description>A simple pong game. This is using the great javascript pong implementation from Jake Gordon. http://codeincomplete.com/posts/2011/5/14/javascript_pong/</description>
+ <licence>AGPL, MIT</licence>
+ <author>Jake Gordon, Frank Karlitschek</author>
+ <requiremin>7.0</requiremin>
+ <shipped>false</shipped>
+</info>
diff --git a/pong/appinfo/version b/pong/appinfo/version
new file mode 100755
index 000000000..9f8e9b69a
--- /dev/null
+++ b/pong/appinfo/version
@@ -0,0 +1 @@
+1.0 \ No newline at end of file
diff --git a/pong/css/pong.css b/pong/css/pong.css
new file mode 100755
index 000000000..50baed942
--- /dev/null
+++ b/pong/css/pong.css
@@ -0,0 +1,44 @@
+body { background-color: black; color: #AAA; font-size: 12pt; padding: 1em; }
+
+#unsupported { border: 1px solid yellow; color: black; background-color: #FFFFAD; padding: 2em; margin: 1em; display: inline-block; }
+
+#sidebar { float: right; font-size: 0.825em; background-color: #333; border: 1px solid white; padding: 1em; margin: 1em; }
+#sidebar h2 { color: white; text-align: center; margin: 0; }
+#sidebar .parts { padding-left: 1em; list-style-type: none; margin-bottom: 2em; text-align: right; }
+#sidebar .parts li a { color: white; text-decoration: none; }
+#sidebar .parts li a:visited { color: white; }
+#sidebar .parts li a:hover { color: white; text-decoration: underline; }
+#sidebar .parts li a.selected { color: #F08010; }
+#sidebar .parts li a i { color: #AAA; }
+#sidebar .parts li a.selected i { color: #F08010; }
+#sidebar .settings { line-height: 1.2em; height: 1.2em; text-align: right; }
+#sidebar .settings.size { }
+#sidebar .settings.speed { margin-bottom: 1em; }
+#sidebar .settings label { vertical-align: middle; }
+#sidebar .settings input { vertical-align: middle; }
+#sidebar .settings select { vertical-align: middle; }
+#sidebar .description { margin-bottom: 2em; }
+#sidebar .description b { font-weight: normal; color: #FFF; }
+
+
+@media screen and (min-width: 0px) {
+ #sidebar { display: none; }
+ #game { display: block; width: 480px; height: 360px; margin: 0 auto; margin-top: 1em;}
+}
+
+@media screen and (min-width: 800px) {
+ #game { width: 640px; height: 480px; }
+}
+
+@media screen and (min-width: 1000px) {
+ #sidebar { display: block; }
+ #game { margin-left: 18em; }
+}
+
+@media screen and (min-width: 1200px) {
+ #game { width: 800px; height: 600px; }
+}
+
+@media screen and (min-width: 1600px) {
+ #game { width: 1024px; height: 768px; }
+}
diff --git a/pong/img/pong.png b/pong/img/pong.png
new file mode 100755
index 000000000..a0adb050b
--- /dev/null
+++ b/pong/img/pong.png
Binary files differ
diff --git a/pong/img/press1.png b/pong/img/press1.png
new file mode 100755
index 000000000..cd0874cb9
--- /dev/null
+++ b/pong/img/press1.png
Binary files differ
diff --git a/pong/img/press2.png b/pong/img/press2.png
new file mode 100755
index 000000000..52ca2b254
--- /dev/null
+++ b/pong/img/press2.png
Binary files differ
diff --git a/pong/img/winner.png b/pong/img/winner.png
new file mode 100755
index 000000000..50f4558f8
--- /dev/null
+++ b/pong/img/winner.png
Binary files differ
diff --git a/pong/index.php b/pong/index.php
new file mode 100755
index 000000000..ae82fe9f9
--- /dev/null
+++ b/pong/index.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * ownCloud - pong
+ *
+ * @author Frank Karlitschek
+ * @copyright 2014 Frank Karlitschek frank@owncloud.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+OCP\User::checkLoggedIn();
+
+OCP\App::setActiveNavigationEntry('pong');
+
+OCP\Util::addStyle( 'pong', 'pong' );
+OCP\Util::addScript( 'pong', 'game' );
+OCP\Util::addScript( 'pong', 'pong' );
+OCP\Util::addScript( 'pong', 'script' );
+
+$tmpl = new OCP\Template('pong', 'main', 'user');
+$tmpl->printPage();
+
diff --git a/pong/js/game.js b/pong/js/game.js
new file mode 100755
index 000000000..370d00749
--- /dev/null
+++ b/pong/js/game.js
@@ -0,0 +1,300 @@
+//=============================================================================
+//
+// We need some ECMAScript 5 methods but we need to implement them ourselves
+// for older browsers (compatibility: http://kangax.github.com/es5-compat-table/)
+//
+// Function.bind: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
+// Object.create: http://javascript.crockford.com/prototypal.html
+// Object.extend: (defacto standard like jquery $.extend or prototype's Object.extend)
+//
+// Object.construct: our own wrapper around Object.create that ALSO calls
+// an initialize constructor method if one exists
+//
+//=============================================================================
+
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function(obj) {
+ var slice = [].slice,
+ args = slice.call(arguments, 1),
+ self = this,
+ nop = function () {},
+ bound = function () {
+ return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments)));
+ };
+ nop.prototype = self.prototype;
+ bound.prototype = new nop();
+ return bound;
+ };
+}
+
+if (!Object.create) {
+ Object.create = function(base) {
+ function F() {};
+ F.prototype = base;
+ return new F();
+ }
+}
+
+if (!Object.construct) {
+ Object.construct = function(base) {
+ var instance = Object.create(base);
+ if (instance.initialize)
+ instance.initialize.apply(instance, [].slice.call(arguments, 1));
+ return instance;
+ }
+}
+
+if (!Object.extend) {
+ Object.extend = function(destination, source) {
+ for (var property in source) {
+ if (source.hasOwnProperty(property))
+ destination[property] = source[property];
+ }
+ return destination;
+ };
+}
+
+/* NOT READY FOR PRIME TIME
+if (!window.requestAnimationFrame) {// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback, element) {
+ window.setTimeout(callback, 1000 / 60);
+ }
+}
+*/
+
+//=============================================================================
+// GAME
+//=============================================================================
+
+Game = {
+
+ compatible: function() {
+ return Object.create &&
+ Object.extend &&
+ Function.bind &&
+ document.addEventListener && // HTML5 standard, all modern browsers that support canvas should also support add/removeEventListener
+ Game.ua.hasCanvas
+ },
+
+ start: function(id, game, cfg) {
+ if (Game.compatible())
+ return Object.construct(Game.Runner, id, game, cfg).game; // return the game instance, not the runner (caller can always get at the runner via game.runner)
+ },
+
+ ua: function() { // should avoid user agent sniffing... but sometimes you just gotta do what you gotta do
+ var ua = navigator.userAgent.toLowerCase();
+ var key = ((ua.indexOf("opera") > -1) ? "opera" : null);
+ key = key || ((ua.indexOf("firefox") > -1) ? "firefox" : null);
+ key = key || ((ua.indexOf("chrome") > -1) ? "chrome" : null);
+ key = key || ((ua.indexOf("safari") > -1) ? "safari" : null);
+ key = key || ((ua.indexOf("msie") > -1) ? "ie" : null);
+
+ try {
+ var re = (key == "ie") ? "msie (\\d)" : key + "\\/(\\d\\.\\d)"
+ var matches = ua.match(new RegExp(re, "i"));
+ var version = matches ? parseFloat(matches[1]) : null;
+ } catch (e) {}
+
+ return {
+ full: ua,
+ name: key + (version ? " " + version.toString() : ""),
+ version: version,
+ isFirefox: (key == "firefox"),
+ isChrome: (key == "chrome"),
+ isSafari: (key == "safari"),
+ isOpera: (key == "opera"),
+ isIE: (key == "ie"),
+ hasCanvas: (document.createElement('canvas').getContext),
+ hasAudio: (typeof(Audio) != 'undefined')
+ }
+ }(),
+
+ addEvent: function(obj, type, fn) { obj.addEventListener(type, fn, false); },
+ removeEvent: function(obj, type, fn) { obj.removeEventListener(type, fn, false); },
+
+ ready: function(fn) {
+ if (Game.compatible())
+ Game.addEvent(document, 'DOMContentLoaded', fn);
+ },
+
+ createCanvas: function() {
+ return document.createElement('canvas');
+ },
+
+ createAudio: function(src) {
+ try {
+ var a = new Audio(src);
+ a.volume = 0.1; // lets be real quiet please
+ return a;
+ } catch (e) {
+ return null;
+ }
+ },
+
+ loadImages: function(sources, callback) { /* load multiple images and callback when ALL have finished loading */
+ var images = {};
+ var count = sources ? sources.length : 0;
+ if (count == 0) {
+ callback(images);
+ }
+ else {
+ for(var n = 0 ; n < sources.length ; n++) {
+ var source = sources[n];
+ var image = document.createElement('img');
+ images[source] = image;
+ Game.addEvent(image, 'load', function() { if (--count == 0) callback(images); });
+ image.src = source;
+ }
+ }
+ },
+
+ random: function(min, max) {
+ return (min + (Math.random() * (max - min)));
+ },
+
+ timestamp: function() {
+ return new Date().getTime();
+ },
+
+ KEY: {
+ BACKSPACE: 8,
+ TAB: 9,
+ RETURN: 13,
+ ESC: 27,
+ SPACE: 32,
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40,
+ DELETE: 46,
+ HOME: 36,
+ END: 35,
+ PAGEUP: 33,
+ PAGEDOWN: 34,
+ INSERT: 45,
+ ZERO: 48,
+ ONE: 49,
+ TWO: 50,
+ A: 65,
+ L: 76,
+ P: 80,
+ Q: 81,
+ TILDA: 192
+ },
+
+ //-----------------------------------------------------------------------------
+
+ Runner: {
+
+ initialize: function(id, game, cfg) {
+ this.cfg = Object.extend(game.Defaults || {}, cfg || {}); // use game defaults (if any) and extend with custom cfg (if any)
+ this.fps = this.cfg.fps || 60;
+ this.interval = 1000.0 / this.fps;
+ this.canvas = document.getElementById(id);
+ this.width = this.cfg.width || this.canvas.offsetWidth;
+ this.height = this.cfg.height || this.canvas.offsetHeight;
+ this.front = this.canvas;
+ this.front.width = this.width;
+ this.front.height = this.height;
+ this.back = Game.createCanvas();
+ this.back.width = this.width;
+ this.back.height = this.height;
+ this.front2d = this.front.getContext('2d');
+ this.back2d = this.back.getContext('2d');
+ this.addEvents();
+ this.resetStats();
+
+ this.game = Object.construct(game, this, this.cfg); // finally construct the game object itself
+ },
+
+ start: function() { // game instance should call runner.start() when its finished initializing and is ready to start the game loop
+ this.lastFrame = Game.timestamp();
+ this.timer = setInterval(this.loop.bind(this), this.interval);
+ },
+
+ stop: function() {
+ clearInterval(this.timer);
+ },
+
+ loop: function() {
+ var start = Game.timestamp(); this.update((start - this.lastFrame)/1000.0); // send dt as seconds
+ var middle = Game.timestamp(); this.draw();
+ var end = Game.timestamp();
+ this.updateStats(middle - start, end - middle);
+ this.lastFrame = start;
+ },
+
+ update: function(dt) {
+ this.game.update(dt);
+ },
+
+ draw: function() {
+ this.back2d.clearRect(0, 0, this.width, this.height);
+ this.game.draw(this.back2d);
+ this.drawStats(this.back2d);
+ this.front2d.clearRect(0, 0, this.width, this.height);
+ this.front2d.drawImage(this.back, 0, 0);
+ },
+
+ resetStats: function() {
+ this.stats = {
+ count: 0,
+ fps: 0,
+ update: 0,
+ draw: 0,
+ frame: 0 // update + draw
+ };
+ },
+
+ updateStats: function(update, draw) {
+ if (this.cfg.stats) {
+ this.stats.update = Math.max(1, update);
+ this.stats.draw = Math.max(1, draw);
+ this.stats.frame = this.stats.update + this.stats.draw;
+ this.stats.count = this.stats.count == this.fps ? 0 : this.stats.count + 1;
+ this.stats.fps = Math.min(this.fps, 1000 / this.stats.frame);
+ }
+ },
+
+ drawStats: function(ctx) {
+ if (this.cfg.stats) {
+ ctx.fillText("frame: " + this.stats.count, this.width - 100, this.height - 60);
+ ctx.fillText("fps: " + this.stats.fps, this.width - 100, this.height - 50);
+ ctx.fillText("update: " + this.stats.update + "ms", this.width - 100, this.height - 40);
+ ctx.fillText("draw: " + this.stats.draw + "ms", this.width - 100, this.height - 30);
+ }
+ },
+
+ addEvents: function() {
+ Game.addEvent(document, 'keydown', this.onkeydown.bind(this));
+ Game.addEvent(document, 'keyup', this.onkeyup.bind(this));
+ },
+
+ onkeydown: function(ev) { if (this.game.onkeydown) this.game.onkeydown(ev.keyCode); },
+ onkeyup: function(ev) { if (this.game.onkeyup) this.game.onkeyup(ev.keyCode); },
+
+ hideCursor: function() { this.canvas.style.cursor = 'none'; },
+ showCursor: function() { this.canvas.style.cursor = 'auto'; },
+
+ alert: function(msg) {
+ this.stop(); // alert blocks thread, so need to stop game loop in order to avoid sending huge dt values to next update
+ result = window.alert(msg);
+ this.start();
+ return result;
+ },
+
+ confirm: function(msg) {
+ this.stop(); // alert blocks thread, so need to stop game loop in order to avoid sending huge dt values to next update
+ result = window.confirm(msg);
+ this.start();
+ return result;
+ }
+
+ //-------------------------------------------------------------------------
+
+ } // Game.Runner
+} // Game
diff --git a/pong/js/pong.js b/pong/js/pong.js
new file mode 100755
index 000000000..38b121e0a
--- /dev/null
+++ b/pong/js/pong.js
@@ -0,0 +1,641 @@
+//=============================================================================
+// PONG
+//=============================================================================
+
+Pong = {
+
+ Defaults: {
+ width: 640, // logical canvas width (browser will scale to physical canvas size - which is controlled by @media css queries)
+ height: 480, // logical canvas height (ditto)
+ wallWidth: 12,
+ paddleWidth: 12,
+ paddleHeight: 60,
+ paddleSpeed: 2, // should be able to cross court vertically in 2 seconds
+ ballSpeed: 4, // should be able to cross court horizontally in 4 seconds, at starting speed ...
+ ballAccel: 8, // ... but accelerate as time passes
+ ballRadius: 5,
+ sound: true
+ },
+
+ Colors: {
+ walls: 'white',
+ ball: 'white',
+ score: 'white',
+ footprint: '#333',
+ predictionGuess: 'yellow',
+ predictionExact: 'red'
+ },
+
+ Images: [
+ OC.imagePath('pong', 'press1.png'),
+ OC.imagePath('pong', 'press2.png'),
+ OC.imagePath('pong', 'winner.png'),
+ ],
+
+ Levels: [
+ {aiReaction: 0.2, aiError: 40}, // 0: ai is losing by 8
+ {aiReaction: 0.3, aiError: 50}, // 1: ai is losing by 7
+ {aiReaction: 0.4, aiError: 60}, // 2: ai is losing by 6
+ {aiReaction: 0.5, aiError: 70}, // 3: ai is losing by 5
+ {aiReaction: 0.6, aiError: 80}, // 4: ai is losing by 4
+ {aiReaction: 0.7, aiError: 90}, // 5: ai is losing by 3
+ {aiReaction: 0.8, aiError: 100}, // 6: ai is losing by 2
+ {aiReaction: 0.9, aiError: 110}, // 7: ai is losing by 1
+ {aiReaction: 1.0, aiError: 120}, // 8: tie
+ {aiReaction: 1.1, aiError: 130}, // 9: ai is winning by 1
+ {aiReaction: 1.2, aiError: 140}, // 10: ai is winning by 2
+ {aiReaction: 1.3, aiError: 150}, // 11: ai is winning by 3
+ {aiReaction: 1.4, aiError: 160}, // 12: ai is winning by 4
+ {aiReaction: 1.5, aiError: 170}, // 13: ai is winning by 5
+ {aiReaction: 1.6, aiError: 180}, // 14: ai is winning by 6
+ {aiReaction: 1.7, aiError: 190}, // 15: ai is winning by 7
+ {aiReaction: 1.8, aiError: 200} // 16: ai is winning by 8
+ ],
+
+ //-----------------------------------------------------------------------------
+
+ initialize: function(runner, cfg) {
+ Game.loadImages(Pong.Images, function(images) {
+ this.cfg = cfg;
+ this.runner = runner;
+ this.width = runner.width;
+ this.height = runner.height;
+ this.images = images;
+ this.playing = false;
+ this.scores = [0, 0];
+ this.menu = Object.construct(Pong.Menu, this);
+ this.court = Object.construct(Pong.Court, this);
+ this.leftPaddle = Object.construct(Pong.Paddle, this);
+ this.rightPaddle = Object.construct(Pong.Paddle, this, true);
+ this.ball = Object.construct(Pong.Ball, this);
+ this.sounds = Object.construct(Pong.Sounds, this);
+ this.runner.start();
+ }.bind(this));
+ },
+
+ startDemo: function() { this.start(0); },
+ startSinglePlayer: function() { this.start(1); },
+ startDoublePlayer: function() { this.start(2); },
+
+ start: function(numPlayers) {
+ if (!this.playing) {
+ this.scores = [0, 0];
+ this.playing = true;
+ this.leftPaddle.setAuto(numPlayers < 1, this.level(0));
+ this.rightPaddle.setAuto(numPlayers < 2, this.level(1));
+ this.ball.reset();
+ this.runner.hideCursor();
+ }
+ },
+
+ stop: function(ask) {
+ if (this.playing) {
+ if (!ask || this.runner.confirm('Abandon game in progress ?')) {
+ this.playing = false;
+ this.leftPaddle.setAuto(false);
+ this.rightPaddle.setAuto(false);
+ this.runner.showCursor();
+ }
+ }
+ },
+
+ level: function(playerNo) {
+ return 8 + (this.scores[playerNo] - this.scores[playerNo ? 0 : 1]);
+ },
+
+ goal: function(playerNo) {
+ this.sounds.goal();
+ this.scores[playerNo] += 1;
+ if (this.scores[playerNo] == 9) {
+ this.menu.declareWinner(playerNo);
+ this.stop();
+ }
+ else {
+ this.ball.reset(playerNo);
+ this.leftPaddle.setLevel(this.level(0));
+ this.rightPaddle.setLevel(this.level(1));
+ }
+ },
+
+ update: function(dt) {
+ this.leftPaddle.update(dt, this.ball);
+ this.rightPaddle.update(dt, this.ball);
+ if (this.playing) {
+ var dx = this.ball.dx;
+ var dy = this.ball.dy;
+ this.ball.update(dt, this.leftPaddle, this.rightPaddle);
+ if (this.ball.dx < 0 && dx > 0)
+ this.sounds.ping();
+ else if (this.ball.dx > 0 && dx < 0)
+ this.sounds.pong();
+ else if (this.ball.dy * dy < 0)
+ this.sounds.wall();
+
+ if (this.ball.left > this.width)
+ this.goal(0);
+ else if (this.ball.right < 0)
+ this.goal(1);
+ }
+ },
+
+ draw: function(ctx) {
+ this.court.draw(ctx, this.scores[0], this.scores[1]);
+ this.leftPaddle.draw(ctx);
+ this.rightPaddle.draw(ctx);
+ if (this.playing)
+ this.ball.draw(ctx);
+ else
+ this.menu.draw(ctx);
+ },
+
+ onkeydown: function(keyCode) {
+ switch(keyCode) {
+ case Game.KEY.ZERO: this.startDemo(); break;
+ case Game.KEY.ONE: this.startSinglePlayer(); break;
+ case Game.KEY.TWO: this.startDoublePlayer(); break;
+ case Game.KEY.ESC: this.stop(true); break;
+ case Game.KEY.Q: if (!this.leftPaddle.auto) this.leftPaddle.moveUp(); break;
+ case Game.KEY.A: if (!this.leftPaddle.auto) this.leftPaddle.moveDown(); break;
+ case Game.KEY.P: if (!this.rightPaddle.auto) this.rightPaddle.moveUp(); break;
+ case Game.KEY.L: if (!this.rightPaddle.auto) this.rightPaddle.moveDown(); break;
+ }
+ },
+
+ onkeyup: function(keyCode) {
+ switch(keyCode) {
+ case Game.KEY.Q: if (!this.leftPaddle.auto) this.leftPaddle.stopMovingUp(); break;
+ case Game.KEY.A: if (!this.leftPaddle.auto) this.leftPaddle.stopMovingDown(); break;
+ case Game.KEY.P: if (!this.rightPaddle.auto) this.rightPaddle.stopMovingUp(); break;
+ case Game.KEY.L: if (!this.rightPaddle.auto) this.rightPaddle.stopMovingDown(); break;
+ }
+ },
+
+ showStats: function(on) { this.cfg.stats = on; },
+ showFootprints: function(on) { this.cfg.footprints = on; this.ball.footprints = []; },
+ showPredictions: function(on) { this.cfg.predictions = on; },
+ enableSound: function(on) { this.cfg.sound = on; },
+
+ //=============================================================================
+ // MENU
+ //=============================================================================
+
+ Menu: {
+
+ initialize: function(pong) {
+ var press1 = pong.images[OC.imagePath('pong', 'press1.png')];
+ var press2 = pong.images[OC.imagePath('pong', 'press2.png')];
+ var winner = pong.images[OC.imagePath('pong', 'winner.png')];
+ this.press1 = { image: press1, x: 10, y: pong.cfg.wallWidth };
+ this.press2 = { image: press2, x: (pong.width - press2.width - 10), y: pong.cfg.wallWidth };
+ this.winner1 = { image: winner, x: (pong.width/2) - winner.width - pong.cfg.wallWidth, y: 6 * pong.cfg.wallWidth };
+ this.winner2 = { image: winner, x: (pong.width/2) + pong.cfg.wallWidth, y: 6 * pong.cfg.wallWidth };
+ },
+
+ declareWinner: function(playerNo) {
+ this.winner = playerNo;
+ },
+
+ draw: function(ctx) {
+ ctx.drawImage(this.press1.image, this.press1.x, this.press1.y);
+ ctx.drawImage(this.press2.image, this.press2.x, this.press2.y);
+ if (this.winner == 0)
+ ctx.drawImage(this.winner1.image, this.winner1.x, this.winner1.y);
+ else if (this.winner == 1)
+ ctx.drawImage(this.winner2.image, this.winner2.x, this.winner2.y);
+ }
+
+ },
+
+ //=============================================================================
+ // SOUNDS
+ //=============================================================================
+
+ Sounds: {
+
+ initialize: function(pong) {
+ this.game = pong;
+ this.supported = Game.ua.hasAudio;
+ if (this.supported) {
+ this.files = {
+ ping: Game.createAudio("sounds/ping.wav"),
+ pong: Game.createAudio("sounds/pong.wav"),
+ wall: Game.createAudio("sounds/wall.wav"),
+ goal: Game.createAudio("sounds/goal.wav")
+ };
+ }
+ },
+
+ play: function(name) {
+ if (this.supported && this.game.cfg.sound && this.files[name])
+ this.files[name].play();
+ },
+
+ ping: function() { this.play('ping'); },
+ pong: function() { this.play('pong'); },
+ wall: function() { /*this.play('wall');*/ },
+ goal: function() { /*this.play('goal');*/ }
+
+ },
+
+ //=============================================================================
+ // COURT
+ //=============================================================================
+
+ Court: {
+
+ initialize: function(pong) {
+ var w = pong.width;
+ var h = pong.height;
+ var ww = pong.cfg.wallWidth;
+
+ this.ww = ww;
+ this.walls = [];
+ this.walls.push({x: 0, y: 0, width: w, height: ww});
+ this.walls.push({x: 0, y: h - ww, width: w, height: ww});
+ var nMax = (h / (ww*2));
+ for(var n = 0 ; n < nMax ; n++) { // draw dashed halfway line
+ this.walls.push({x: (w / 2) - (ww / 2),
+ y: (ww / 2) + (ww * 2 * n),
+ width: ww, height: ww});
+ }
+
+ var sw = 3*ww;
+ var sh = 4*ww;
+ this.score1 = {x: 0.5 + (w/2) - 1.5*ww - sw, y: 2*ww, w: sw, h: sh};
+ this.score2 = {x: 0.5 + (w/2) + 1.5*ww, y: 2*ww, w: sw, h: sh};
+ },
+
+ draw: function(ctx, scorePlayer1, scorePlayer2) {
+ ctx.fillStyle = Pong.Colors.walls;
+ for(var n = 0 ; n < this.walls.length ; n++)
+ ctx.fillRect(this.walls[n].x, this.walls[n].y, this.walls[n].width, this.walls[n].height);
+ this.drawDigit(ctx, scorePlayer1, this.score1.x, this.score1.y, this.score1.w, this.score1.h);
+ this.drawDigit(ctx, scorePlayer2, this.score2.x, this.score2.y, this.score2.w, this.score2.h);
+ },
+
+ drawDigit: function(ctx, n, x, y, w, h) {
+ ctx.fillStyle = Pong.Colors.score;
+ var dw = dh = this.ww*4/5;
+ var blocks = Pong.Court.DIGITS[n];
+ if (blocks[0])
+ ctx.fillRect(x, y, w, dh);
+ if (blocks[1])
+ ctx.fillRect(x, y, dw, h/2);
+ if (blocks[2])
+ ctx.fillRect(x+w-dw, y, dw, h/2);
+ if (blocks[3])
+ ctx.fillRect(x, y + h/2 - dh/2, w, dh);
+ if (blocks[4])
+ ctx.fillRect(x, y + h/2, dw, h/2);
+ if (blocks[5])
+ ctx.fillRect(x+w-dw, y + h/2, dw, h/2);
+ if (blocks[6])
+ ctx.fillRect(x, y+h-dh, w, dh);
+ },
+
+ DIGITS: [
+ [1, 1, 1, 0, 1, 1, 1], // 0
+ [0, 0, 1, 0, 0, 1, 0], // 1
+ [1, 0, 1, 1, 1, 0, 1], // 2
+ [1, 0, 1, 1, 0, 1, 1], // 3
+ [0, 1, 1, 1, 0, 1, 0], // 4
+ [1, 1, 0, 1, 0, 1, 1], // 5
+ [1, 1, 0, 1, 1, 1, 1], // 6
+ [1, 0, 1, 0, 0, 1, 0], // 7
+ [1, 1, 1, 1, 1, 1, 1], // 8
+ [1, 1, 1, 1, 0, 1, 0] // 9
+ ]
+
+ },
+
+ //=============================================================================
+ // PADDLE
+ //=============================================================================
+
+ Paddle: {
+
+ initialize: function(pong, rhs) {
+ this.pong = pong;
+ this.width = pong.cfg.paddleWidth;
+ this.height = pong.cfg.paddleHeight;
+ this.minY = pong.cfg.wallWidth;
+ this.maxY = pong.height - pong.cfg.wallWidth - this.height;
+ this.speed = (this.maxY - this.minY) / pong.cfg.paddleSpeed;
+ this.setpos(rhs ? pong.width - this.width : 0, this.minY + (this.maxY - this.minY)/2);
+ this.setdir(0);
+ },
+
+ setpos: function(x, y) {
+ this.x = x;
+ this.y = y;
+ this.left = this.x;
+ this.right = this.left + this.width;
+ this.top = this.y;
+ this.bottom = this.y + this.height;
+ },
+
+ setdir: function(dy) {
+ this.up = (dy < 0 ? -dy : 0);
+ this.down = (dy > 0 ? dy : 0);
+ },
+
+ setAuto: function(on, level) {
+ if (on && !this.auto) {
+ this.auto = true;
+ this.setLevel(level);
+ }
+ else if (!on && this.auto) {
+ this.auto = false;
+ this.setdir(0);
+ }
+ },
+
+ setLevel: function(level) {
+ if (this.auto)
+ this.level = Pong.Levels[level];
+ },
+
+ update: function(dt, ball) {
+ if (this.auto)
+ this.ai(dt, ball);
+
+ var amount = this.down - this.up;
+ if (amount != 0) {
+ var y = this.y + (amount * dt * this.speed);
+ if (y < this.minY)
+ y = this.minY;
+ else if (y > this.maxY)
+ y = this.maxY;
+ this.setpos(this.x, y);
+ }
+ },
+
+ ai: function(dt, ball) {
+ if (((ball.x < this.left) && (ball.dx < 0)) ||
+ ((ball.x > this.right) && (ball.dx > 0))) {
+ this.stopMovingUp();
+ this.stopMovingDown();
+ return;
+ }
+
+ this.predict(ball, dt);
+
+ if (this.prediction) {
+ if (this.prediction.y < (this.top + this.height/2 - 5)) {
+ this.stopMovingDown();
+ this.moveUp();
+ }
+ else if (this.prediction.y > (this.bottom - this.height/2 + 5)) {
+ this.stopMovingUp();
+ this.moveDown();
+ }
+ else {
+ this.stopMovingUp();
+ this.stopMovingDown();
+ }
+ }
+ },
+
+ predict: function(ball, dt) {
+ // only re-predict if the ball changed direction, or its been some amount of time since last prediction
+ if (this.prediction &&
+ ((this.prediction.dx * ball.dx) > 0) &&
+ ((this.prediction.dy * ball.dy) > 0) &&
+ (this.prediction.since < this.level.aiReaction)) {
+ this.prediction.since += dt;
+ return;
+ }
+
+ var pt = Pong.Helper.ballIntercept(ball, {left: this.left, right: this.right, top: -10000, bottom: 10000}, ball.dx * 10, ball.dy * 10);
+ if (pt) {
+ var t = this.minY + ball.radius;
+ var b = this.maxY + this.height - ball.radius;
+
+ while ((pt.y < t) || (pt.y > b)) {
+ if (pt.y < t) {
+ pt.y = t + (t - pt.y);
+ }
+ else if (pt.y > b) {
+ pt.y = t + (b - t) - (pt.y - b);
+ }
+ }
+ this.prediction = pt;
+ }
+ else {
+ this.prediction = null;
+ }
+
+ if (this.prediction) {
+ this.prediction.since = 0;
+ this.prediction.dx = ball.dx;
+ this.prediction.dy = ball.dy;
+ this.prediction.radius = ball.radius;
+ this.prediction.exactX = this.prediction.x;
+ this.prediction.exactY = this.prediction.y;
+ var closeness = (ball.dx < 0 ? ball.x - this.right : this.left - ball.x) / this.pong.width;
+ var error = this.level.aiError * closeness;
+ this.prediction.y = this.prediction.y + Game.random(-error, error);
+ }
+ },
+
+ draw: function(ctx) {
+ ctx.fillStyle = Pong.Colors.walls;
+ ctx.fillRect(this.x, this.y, this.width, this.height);
+ if (this.prediction && this.pong.cfg.predictions) {
+ ctx.strokeStyle = Pong.Colors.predictionExact;
+ ctx.strokeRect(this.prediction.x - this.prediction.radius, this.prediction.exactY - this.prediction.radius, this.prediction.radius*2, this.prediction.radius*2);
+ ctx.strokeStyle = Pong.Colors.predictionGuess;
+ ctx.strokeRect(this.prediction.x - this.prediction.radius, this.prediction.y - this.prediction.radius, this.prediction.radius*2, this.prediction.radius*2);
+ }
+ },
+
+ moveUp: function() { this.up = 1; },
+ moveDown: function() { this.down = 1; },
+ stopMovingUp: function() { this.up = 0; },
+ stopMovingDown: function() { this.down = 0; }
+
+ },
+
+ //=============================================================================
+ // BALL
+ //=============================================================================
+
+ Ball: {
+
+ initialize: function(pong) {
+ this.pong = pong;
+ this.radius = pong.cfg.ballRadius;
+ this.minX = this.radius;
+ this.maxX = pong.width - this.radius;
+ this.minY = pong.cfg.wallWidth + this.radius;
+ this.maxY = pong.height - pong.cfg.wallWidth - this.radius;
+ this.speed = (this.maxX - this.minX) / pong.cfg.ballSpeed;
+ this.accel = pong.cfg.ballAccel;
+ },
+
+ reset: function(playerNo) {
+ this.footprints = [];
+ this.setpos(playerNo == 1 ? this.maxX : this.minX, Game.random(this.minY, this.maxY));
+ this.setdir(playerNo == 1 ? -this.speed : this.speed, this.speed);
+ },
+
+ setpos: function(x, y) {
+ this.x = x;
+ this.y = y;
+ this.left = this.x - this.radius;
+ this.top = this.y - this.radius;
+ this.right = this.x + this.radius;
+ this.bottom = this.y + this.radius;
+ },
+
+ setdir: function(dx, dy) {
+ this.dxChanged = ((this.dx < 0) != (dx < 0)); // did horizontal direction change
+ this.dyChanged = ((this.dy < 0) != (dy < 0)); // did vertical direction change
+ this.dx = dx;
+ this.dy = dy;
+ },
+
+ footprint: function() {
+ if (this.pong.cfg.footprints) {
+ if (!this.footprintCount || this.dxChanged || this.dyChanged) {
+ this.footprints.push({x: this.x, y: this.y});
+ if (this.footprints.length > 50)
+ this.footprints.shift();
+ this.footprintCount = 5;
+ }
+ else {
+ this.footprintCount--;
+ }
+ }
+ },
+
+ update: function(dt, leftPaddle, rightPaddle) {
+
+ pos = Pong.Helper.accelerate(this.x, this.y, this.dx, this.dy, this.accel, dt);
+
+ if ((pos.dy > 0) && (pos.y > this.maxY)) {
+ pos.y = this.maxY;
+ pos.dy = -pos.dy;
+ }
+ else if ((pos.dy < 0) && (pos.y < this.minY)) {
+ pos.y = this.minY;
+ pos.dy = -pos.dy;
+ }
+
+ var paddle = (pos.dx < 0) ? leftPaddle : rightPaddle;
+ var pt = Pong.Helper.ballIntercept(this, paddle, pos.nx, pos.ny);
+
+ if (pt) {
+ switch(pt.d) {
+ case 'left':
+ case 'right':
+ pos.x = pt.x;
+ pos.dx = -pos.dx;
+ break;
+ case 'top':
+ case 'bottom':
+ pos.y = pt.y;
+ pos.dy = -pos.dy;
+ break;
+ }
+
+ // add/remove spin based on paddle direction
+ if (paddle.up)
+ pos.dy = pos.dy * (pos.dy < 0 ? 0.5 : 1.5);
+ else if (paddle.down)
+ pos.dy = pos.dy * (pos.dy > 0 ? 0.5 : 1.5);
+ }
+
+ this.setpos(pos.x, pos.y);
+ this.setdir(pos.dx, pos.dy);
+ this.footprint();
+ },
+
+ draw: function(ctx) {
+ var w = h = this.radius * 2;
+ ctx.fillStyle = Pong.Colors.ball;
+ ctx.fillRect(this.x - this.radius, this.y - this.radius, w, h);
+ if (this.pong.cfg.footprints) {
+ var max = this.footprints.length;
+ ctx.strokeStyle = Pong.Colors.footprint;
+ for(var n = 0 ; n < max ; n++)
+ ctx.strokeRect(this.footprints[n].x - this.radius, this.footprints[n].y - this.radius, w, h);
+ }
+ }
+
+ },
+
+ //=============================================================================
+ // HELPER
+ //=============================================================================
+
+ Helper: {
+
+ accelerate: function(x, y, dx, dy, accel, dt) {
+ var x2 = x + (dt * dx) + (accel * dt * dt * 0.5);
+ var y2 = y + (dt * dy) + (accel * dt * dt * 0.5);
+ var dx2 = dx + (accel * dt) * (dx > 0 ? 1 : -1);
+ var dy2 = dy + (accel * dt) * (dy > 0 ? 1 : -1);
+ return { nx: (x2-x), ny: (y2-y), x: x2, y: y2, dx: dx2, dy: dy2 };
+ },
+
+ intercept: function(x1, y1, x2, y2, x3, y3, x4, y4, d) {
+ var denom = ((y4-y3) * (x2-x1)) - ((x4-x3) * (y2-y1));
+ if (denom != 0) {
+ var ua = (((x4-x3) * (y1-y3)) - ((y4-y3) * (x1-x3))) / denom;
+ if ((ua >= 0) && (ua <= 1)) {
+ var ub = (((x2-x1) * (y1-y3)) - ((y2-y1) * (x1-x3))) / denom;
+ if ((ub >= 0) && (ub <= 1)) {
+ var x = x1 + (ua * (x2-x1));
+ var y = y1 + (ua * (y2-y1));
+ return { x: x, y: y, d: d};
+ }
+ }
+ }
+ return null;
+ },
+
+ ballIntercept: function(ball, rect, nx, ny) {
+ var pt;
+ if (nx < 0) {
+ pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny,
+ rect.right + ball.radius,
+ rect.top - ball.radius,
+ rect.right + ball.radius,
+ rect.bottom + ball.radius,
+ "right");
+ }
+ else if (nx > 0) {
+ pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny,
+ rect.left - ball.radius,
+ rect.top - ball.radius,
+ rect.left - ball.radius,
+ rect.bottom + ball.radius,
+ "left");
+ }
+ if (!pt) {
+ if (ny < 0) {
+ pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny,
+ rect.left - ball.radius,
+ rect.bottom + ball.radius,
+ rect.right + ball.radius,
+ rect.bottom + ball.radius,
+ "bottom");
+ }
+ else if (ny > 0) {
+ pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny,
+ rect.left - ball.radius,
+ rect.top - ball.radius,
+ rect.right + ball.radius,
+ rect.top - ball.radius,
+ "top");
+ }
+ }
+ return pt;
+ }
+
+ }
+
+ //=============================================================================
+
+}; // Pong
diff --git a/pong/js/script.js b/pong/js/script.js
new file mode 100755
index 000000000..ab676d781
--- /dev/null
+++ b/pong/js/script.js
@@ -0,0 +1,23 @@
+$( document ).ready(function() {
+
+ var size = document.getElementById('size');
+ var sound = document.getElementById('sound');
+ var stats = document.getElementById('stats');
+ var footprints = document.getElementById('footprints');
+ var predictions = document.getElementById('predictions');
+
+ var pong = Game.start('game', Pong, {
+ sound: sound.checked,
+ stats: stats.checked,
+ footprints: footprints.checked,
+ predictions: predictions.checked
+ });
+
+ Game.addEvent(sound, 'change', function() { pong.enableSound(sound.checked); });
+ Game.addEvent(stats, 'change', function() { pong.showStats(stats.checked); });
+ Game.addEvent(footprints, 'change', function() { pong.showFootprints(footprints.checked); });
+ Game.addEvent(predictions, 'change', function() { pong.showPredictions(predictions.checked); });
+
+});
+
+
diff --git a/pong/sounds/goal.wav b/pong/sounds/goal.wav
new file mode 100755
index 000000000..965c2d2ad
--- /dev/null
+++ b/pong/sounds/goal.wav
Binary files differ
diff --git a/pong/sounds/ping.wav b/pong/sounds/ping.wav
new file mode 100755
index 000000000..0f45c6804
--- /dev/null
+++ b/pong/sounds/ping.wav
Binary files differ
diff --git a/pong/sounds/pong.wav b/pong/sounds/pong.wav
new file mode 100755
index 000000000..e3eedf156
--- /dev/null
+++ b/pong/sounds/pong.wav
Binary files differ
diff --git a/pong/sounds/wall.wav b/pong/sounds/wall.wav
new file mode 100755
index 000000000..32a112947
--- /dev/null
+++ b/pong/sounds/wall.wav
Binary files differ
diff --git a/pong/templates/main.php b/pong/templates/main.php
new file mode 100755
index 000000000..62f65a1c5
--- /dev/null
+++ b/pong/templates/main.php
@@ -0,0 +1,33 @@
+
+ <div id="sidebar">
+
+
+
+ <div class='settings'>
+ <label for='sound'>sound: </label>
+ <input type='checkbox' id='sound'>
+ </div>
+
+ <div class='settings'>
+ <label for='stats'>stats: </label>
+ <input type='checkbox' id='stats' checked>
+ </div>
+
+ <div class='settings'>
+ <label for='footprints'>footprints: </label>
+ <input type='checkbox' id='footprints'>
+ </div>
+
+ <div class='settings'>
+ <label for='predictions'>predictions: </label>
+ <input type='checkbox' id='predictions'>
+ </div>
+
+ </div>
+
+ <canvas id="game">
+ <div id="unsupported">
+ Sorry, this example cannot be run because your browser does not support the &lt;canvas&gt; element
+ </div>
+ </canvas>
+