diff --git a/01_Programming/BasicExample1/BasicExample.html b/01_Programming/BasicExample1/BasicExample.html
new file mode 100644
index 0000000..607322e
--- /dev/null
+++ b/01_Programming/BasicExample1/BasicExample.html
@@ -0,0 +1,10 @@
+
+
+
+ BasicExample
+
+
+Hello, how are you?
+I'm doing great, thank you!
+
+
diff --git a/01_Programming/BasicExample2/BasicExample.html b/01_Programming/BasicExample2/BasicExample.html
new file mode 100644
index 0000000..308cfc4
--- /dev/null
+++ b/01_Programming/BasicExample2/BasicExample.html
@@ -0,0 +1,21 @@
+
+
+
+BasicExample
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/01_Programming/BasicExample3/BasicExample.html b/01_Programming/BasicExample3/BasicExample.html
new file mode 100644
index 0000000..44a5f3d
--- /dev/null
+++ b/01_Programming/BasicExample3/BasicExample.html
@@ -0,0 +1,12 @@
+
+
+
+BasicExample
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/01_Programming/BasicExample3/BasicExample.js b/01_Programming/BasicExample3/BasicExample.js
new file mode 100644
index 0000000..72f133d
--- /dev/null
+++ b/01_Programming/BasicExample3/BasicExample.js
@@ -0,0 +1,10 @@
+"use strict";
+
+function changeCanvasColor () {
+ var canvas = document.getElementById("mycanvas");
+ var context = canvas.getContext("2d");
+ context.fillStyle = "blue";
+ context.fillRect(0, 0, canvas.width, canvas.height);
+}
+
+document.addEventListener( 'DOMContentLoaded', changeCanvasColor);
\ No newline at end of file
diff --git a/01_Programming/verySimplehtml.html b/01_Programming/verySimplehtml.html
new file mode 100644
index 0000000..e286baf
--- /dev/null
+++ b/01_Programming/verySimplehtml.html
@@ -0,0 +1,14 @@
+
+
+
+BasicExample
+
+
+
+
+
+
diff --git a/02_GameProgrammingBasics/BasicGame/BasicGame.html b/02_GameProgrammingBasics/BasicGame/BasicGame.html
new file mode 100644
index 0000000..a5db9bc
--- /dev/null
+++ b/02_GameProgrammingBasics/BasicGame/BasicGame.html
@@ -0,0 +1,12 @@
+
+
+
+BasicGame
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/02_GameProgrammingBasics/BasicGame/BasicGame.js b/02_GameProgrammingBasics/BasicGame/BasicGame.js
new file mode 100644
index 0000000..332554e
--- /dev/null
+++ b/02_GameProgrammingBasics/BasicGame/BasicGame.js
@@ -0,0 +1,24 @@
+var canvas = undefined;
+var canvasContext = undefined;
+
+function start () {
+ canvas = document.getElementById("myCanvas");
+ canvasContext = canvas.getContext("2d");
+ gameLoop();
+}
+
+document.addEventListener( 'DOMContentLoaded', start);
+
+function update () {
+}
+
+function draw () {
+}
+
+function gameLoop () {
+ canvasContext.fillStyle = "blue";
+ canvasContext.fillRect(0, 0, canvas.width, canvas.height);
+ update();
+ draw();
+ window.setTimeout(gameLoop, 1000 / 60);
+}
\ No newline at end of file
diff --git a/03_CreatingGameWorld/MovingSquare/MovingSquare.html b/03_CreatingGameWorld/MovingSquare/MovingSquare.html
new file mode 100644
index 0000000..b9f34e5
--- /dev/null
+++ b/03_CreatingGameWorld/MovingSquare/MovingSquare.html
@@ -0,0 +1,12 @@
+
+
+
+MovingSquare
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/03_CreatingGameWorld/MovingSquare/MovingSquare.js b/03_CreatingGameWorld/MovingSquare/MovingSquare.js
new file mode 100644
index 0000000..1036a53
--- /dev/null
+++ b/03_CreatingGameWorld/MovingSquare/MovingSquare.js
@@ -0,0 +1,37 @@
+"use strict";
+
+var Game = {
+ canvas : undefined,
+ canvasContext : undefined,
+ rectanglePosition : 0
+};
+
+Game.start = function () {
+ Game.canvas = document.getElementById("myCanvas");
+ Game.canvasContext = Game.canvas.getContext("2d");
+ Game.mainLoop();
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.clearCanvas = function () {
+ Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);
+};
+
+Game.mainLoop = function() {
+ Game.clearCanvas();
+ Game.update();
+ Game.draw();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+ var d = new Date();
+ Game.rectanglePosition = d.getTime() % Game.canvas.width;
+};
+
+Game.draw = function () {
+ Game.canvasContext.fillStyle = "blue";
+ Game.canvasContext.fillRect(Game.rectanglePosition, 100, 50, 50);
+};
+
diff --git a/04_GameAssets/FlyingSprite/FlyingSprite.html b/04_GameAssets/FlyingSprite/FlyingSprite.html
new file mode 100644
index 0000000..3aa1d56
--- /dev/null
+++ b/04_GameAssets/FlyingSprite/FlyingSprite.html
@@ -0,0 +1,12 @@
+
+
+
+FlyingSprite
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/04_GameAssets/FlyingSprite/FlyingSprite.js b/04_GameAssets/FlyingSprite/FlyingSprite.js
new file mode 100644
index 0000000..6d5234b
--- /dev/null
+++ b/04_GameAssets/FlyingSprite/FlyingSprite.js
@@ -0,0 +1,50 @@
+"use strict";
+
+var Game = {
+ canvas : undefined,
+ canvasContext : undefined,
+ backgroundSprite : undefined,
+ balloonSprite : undefined,
+ balloonPosition : { x : 0, y : 50 }
+};
+
+Game.start = function () {
+ Game.canvas = document.getElementById("myCanvas");
+ Game.canvasContext = Game.canvas.getContext("2d");
+ Game.backgroundSprite = new Image();
+ Game.backgroundSprite.src = "spr_background.jpg";
+ Game.balloonSprite = new Image();
+ Game.balloonSprite.src = "spr_balloon.png";
+ window.setTimeout(Game.mainLoop, 500);
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.clearCanvas = function () {
+ Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);
+};
+
+Game.drawImage = function (sprite, position) {
+ Game.canvasContext.save();
+ Game.canvasContext.translate(position.x, position.y);
+ Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height,
+ 0, 0, sprite.width, sprite.height);
+ Game.canvasContext.restore();
+};
+
+Game.mainLoop = function() {
+ Game.clearCanvas();
+ Game.update();
+ Game.draw();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+ var d = new Date();
+ Game.balloonPosition.x = d.getTime() * 0.3 % Game.canvas.width;
+};
+
+Game.draw = function () {
+ Game.drawImage(Game.backgroundSprite, { x : 0, y : 0 });
+ Game.drawImage(Game.balloonSprite, Game.balloonPosition);
+};
\ No newline at end of file
diff --git a/04_GameAssets/FlyingSprite/spr_background.jpg b/04_GameAssets/FlyingSprite/spr_background.jpg
new file mode 100644
index 0000000..9b7cb5f
Binary files /dev/null and b/04_GameAssets/FlyingSprite/spr_background.jpg differ
diff --git a/04_GameAssets/FlyingSprite/spr_balloon.png b/04_GameAssets/FlyingSprite/spr_balloon.png
new file mode 100644
index 0000000..b1453b9
Binary files /dev/null and b/04_GameAssets/FlyingSprite/spr_balloon.png differ
diff --git a/04_GameAssets/FlyingSpriteWithSound/FlyingSpriteWithSound.html b/04_GameAssets/FlyingSpriteWithSound/FlyingSpriteWithSound.html
new file mode 100644
index 0000000..6db815c
--- /dev/null
+++ b/04_GameAssets/FlyingSpriteWithSound/FlyingSpriteWithSound.html
@@ -0,0 +1,12 @@
+
+
+
+FlyingSpriteWithSound
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/04_GameAssets/FlyingSpriteWithSound/FlyingSpriteWithSound.js b/04_GameAssets/FlyingSpriteWithSound/FlyingSpriteWithSound.js
new file mode 100644
index 0000000..ff5ebe3
--- /dev/null
+++ b/04_GameAssets/FlyingSpriteWithSound/FlyingSpriteWithSound.js
@@ -0,0 +1,55 @@
+"use strict";
+
+var Game = {
+ canvas : undefined,
+ canvasContext : undefined,
+ backgroundSprite : undefined,
+ balloonSprite : undefined,
+ balloonPosition : { x : 0, y : 50 },
+ backgroundMusic : undefined
+};
+
+Game.start = function () {
+ Game.canvas = document.getElementById("myCanvas");
+ Game.canvasContext = Game.canvas.getContext("2d");
+ Game.backgroundSprite = new Image();
+ Game.backgroundSprite.src = "spr_background.jpg";
+ Game.balloonSprite = new Image();
+ Game.balloonSprite.src = "spr_balloon.png";
+ Game.backgroundMusic = new Audio();
+ Game.backgroundMusic.src = "snd_music.mp3";
+ Game.backgroundMusic.volume = 0.4;
+ Game.backgroundMusic.play();
+ window.setTimeout(Game.mainLoop, 500);
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.clearCanvas = function () {
+ Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);
+};
+
+Game.drawImage = function (sprite, position) {
+ Game.canvasContext.save();
+ Game.canvasContext.translate(position.x, position.y);
+ Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height,
+ 0, 0, sprite.width, sprite.height);
+ Game.canvasContext.restore();
+};
+
+Game.mainLoop = function() {
+ Game.clearCanvas();
+ Game.update();
+ Game.draw();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+ var d = new Date();
+ Game.balloonPosition.x = d.getTime() * 0.3 % Game.canvas.width;
+};
+
+Game.draw = function () {
+ Game.drawImage(Game.backgroundSprite, { x : 0, y : 0 });
+ Game.drawImage(Game.balloonSprite, Game.balloonPosition);
+};
\ No newline at end of file
diff --git a/04_GameAssets/FlyingSpriteWithSound/snd_music.mp3 b/04_GameAssets/FlyingSpriteWithSound/snd_music.mp3
new file mode 100644
index 0000000..2347c04
Binary files /dev/null and b/04_GameAssets/FlyingSpriteWithSound/snd_music.mp3 differ
diff --git a/04_GameAssets/FlyingSpriteWithSound/spr_background.jpg b/04_GameAssets/FlyingSpriteWithSound/spr_background.jpg
new file mode 100644
index 0000000..9b7cb5f
Binary files /dev/null and b/04_GameAssets/FlyingSpriteWithSound/spr_background.jpg differ
diff --git a/04_GameAssets/FlyingSpriteWithSound/spr_balloon.png b/04_GameAssets/FlyingSpriteWithSound/spr_balloon.png
new file mode 100644
index 0000000..b1453b9
Binary files /dev/null and b/04_GameAssets/FlyingSpriteWithSound/spr_balloon.png differ
diff --git a/04_GameAssets/SpriteDrawing/SpriteDrawing.html b/04_GameAssets/SpriteDrawing/SpriteDrawing.html
new file mode 100644
index 0000000..4ecccc4
--- /dev/null
+++ b/04_GameAssets/SpriteDrawing/SpriteDrawing.html
@@ -0,0 +1,12 @@
+
+
+
+SpriteDrawing
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/04_GameAssets/SpriteDrawing/SpriteDrawing.js b/04_GameAssets/SpriteDrawing/SpriteDrawing.js
new file mode 100644
index 0000000..c7218e4
--- /dev/null
+++ b/04_GameAssets/SpriteDrawing/SpriteDrawing.js
@@ -0,0 +1,43 @@
+"use strict";
+
+var Game = {
+ canvas : undefined,
+ canvasContext : undefined,
+ balloonSprite : undefined
+};
+
+Game.start = function () {
+ Game.canvas = document.getElementById("myCanvas");
+ Game.canvasContext = Game.canvas.getContext("2d");
+ Game.balloonSprite = new Image();
+ Game.balloonSprite.src = "spr_balloon.png";
+ window.setTimeout(Game.mainLoop, 500);
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.clearCanvas = function () {
+ Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);
+};
+
+Game.drawImage = function (sprite, position) {
+ Game.canvasContext.save();
+ Game.canvasContext.translate(position.x, position.y);
+ Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height,
+ 0, 0, sprite.width, sprite.height);
+ Game.canvasContext.restore();
+};
+
+Game.mainLoop = function() {
+ Game.clearCanvas();
+ Game.update();
+ Game.draw();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+};
+
+Game.draw = function () {
+ Game.drawImage(Game.balloonSprite, { x : 100, y : 100 });
+};
\ No newline at end of file
diff --git a/04_GameAssets/SpriteDrawing/spr_balloon.png b/04_GameAssets/SpriteDrawing/spr_balloon.png
new file mode 100644
index 0000000..b1453b9
Binary files /dev/null and b/04_GameAssets/SpriteDrawing/spr_balloon.png differ
diff --git a/05_KnowingPlayer/Balloon1/Balloon.html b/05_KnowingPlayer/Balloon1/Balloon.html
new file mode 100644
index 0000000..a627f00
--- /dev/null
+++ b/05_KnowingPlayer/Balloon1/Balloon.html
@@ -0,0 +1,13 @@
+
+
+
+ Balloon
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/05_KnowingPlayer/Balloon1/Balloon.js b/05_KnowingPlayer/Balloon1/Balloon.js
new file mode 100644
index 0000000..1385419
--- /dev/null
+++ b/05_KnowingPlayer/Balloon1/Balloon.js
@@ -0,0 +1,53 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Game.balloonPosition = { x : evt.clientX, y : evt.clientY };
+}
+
+var Game = {
+ canvas : undefined,
+ canvasContext : undefined,
+ backgroundSprite : undefined,
+ balloonSprite : undefined,
+ balloonPosition : { x : 0, y : 0 }
+};
+
+Game.clearCanvas = function () {
+ Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);
+};
+
+Game.drawImage = function (sprite, position) {
+ Game.canvasContext.save();
+ Game.canvasContext.translate(position.x, position.y);
+ Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height,
+ 0, 0, sprite.width, sprite.height);
+ Game.canvasContext.restore();
+};
+
+Game.start = function () {
+ Game.canvas = document.getElementById("myCanvas");
+ Game.canvasContext = Game.canvas.getContext('2d');
+ document.onmousemove = handleMouseMove;
+ Game.backgroundSprite = new Image();
+ Game.backgroundSprite.src = "spr_background.jpg";
+ Game.balloonSprite = new Image();
+ Game.balloonSprite.src = "spr_balloon.png";
+ window.setTimeout(Game.mainLoop, 500);
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.mainLoop = function() {
+ Game.clearCanvas();
+ Game.update();
+ Game.draw();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+};
+
+Game.draw = function () {
+ Game.drawImage(Game.backgroundSprite, { x : 0, y : 0 });
+ Game.drawImage(Game.balloonSprite, Game.balloonPosition);
+};
\ No newline at end of file
diff --git a/05_KnowingPlayer/Balloon1/game-layout.css b/05_KnowingPlayer/Balloon1/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/05_KnowingPlayer/Balloon1/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/05_KnowingPlayer/Balloon1/spr_background.jpg b/05_KnowingPlayer/Balloon1/spr_background.jpg
new file mode 100644
index 0000000..9b7cb5f
Binary files /dev/null and b/05_KnowingPlayer/Balloon1/spr_background.jpg differ
diff --git a/05_KnowingPlayer/Balloon1/spr_balloon.png b/05_KnowingPlayer/Balloon1/spr_balloon.png
new file mode 100644
index 0000000..b1453b9
Binary files /dev/null and b/05_KnowingPlayer/Balloon1/spr_balloon.png differ
diff --git a/05_KnowingPlayer/Balloon2/Balloon.html b/05_KnowingPlayer/Balloon2/Balloon.html
new file mode 100644
index 0000000..a627f00
--- /dev/null
+++ b/05_KnowingPlayer/Balloon2/Balloon.html
@@ -0,0 +1,13 @@
+
+
+
+ Balloon
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/05_KnowingPlayer/Balloon2/Balloon.js b/05_KnowingPlayer/Balloon2/Balloon.js
new file mode 100644
index 0000000..eeda5a1
--- /dev/null
+++ b/05_KnowingPlayer/Balloon2/Balloon.js
@@ -0,0 +1,55 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Game.mousePosition = { x : evt.pageX, y : evt.pageY };
+}
+
+var Game = {
+ canvas : undefined,
+ canvasContext : undefined,
+ backgroundSprite : undefined,
+ balloonSprite : undefined,
+ mousePosition : { x : 0, y : 0 },
+ balloonOrigin : { x : 0, y : 0 }
+};
+
+Game.clearCanvas = function () {
+ Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);
+};
+
+Game.drawImage = function (sprite, position, origin) {
+ Game.canvasContext.save();
+ Game.canvasContext.translate(position.x, position.y);
+ Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height,
+ -origin.x, -origin.y, sprite.width, sprite.height);
+ Game.canvasContext.restore();
+};
+
+Game.start = function () {
+ Game.canvas = document.getElementById("myCanvas");
+ Game.canvasContext = Game.canvas.getContext('2d');
+ document.onmousemove = handleMouseMove;
+ Game.backgroundSprite = new Image();
+ Game.backgroundSprite.src = "spr_background.jpg";
+ Game.balloonSprite = new Image();
+ Game.balloonSprite.src = "spr_balloon.png";
+ window.setTimeout(Game.mainLoop, 500);
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.mainLoop = function() {
+ Game.clearCanvas();
+ Game.update();
+ Game.draw();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+};
+
+Game.draw = function () {
+ Game.drawImage(Game.backgroundSprite, { x : 0, y : 0 }, { x : 0, y : 0 });
+ Game.balloonOrigin = { x : Game.balloonSprite.width / 2, y : Game.balloonSprite.height };
+ Game.drawImage(Game.balloonSprite, Game.mousePosition, Game.balloonOrigin);
+};
\ No newline at end of file
diff --git a/05_KnowingPlayer/Balloon2/game-layout.css b/05_KnowingPlayer/Balloon2/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/05_KnowingPlayer/Balloon2/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/05_KnowingPlayer/Balloon2/spr_background.jpg b/05_KnowingPlayer/Balloon2/spr_background.jpg
new file mode 100644
index 0000000..9b7cb5f
Binary files /dev/null and b/05_KnowingPlayer/Balloon2/spr_background.jpg differ
diff --git a/05_KnowingPlayer/Balloon2/spr_balloon.png b/05_KnowingPlayer/Balloon2/spr_balloon.png
new file mode 100644
index 0000000..b1453b9
Binary files /dev/null and b/05_KnowingPlayer/Balloon2/spr_balloon.png differ
diff --git a/05_KnowingPlayer/Painter1/Painter.html b/05_KnowingPlayer/Painter1/Painter.html
new file mode 100644
index 0000000..cfb32c2
--- /dev/null
+++ b/05_KnowingPlayer/Painter1/Painter.html
@@ -0,0 +1,13 @@
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/05_KnowingPlayer/Painter1/Painter.js b/05_KnowingPlayer/Painter1/Painter.js
new file mode 100644
index 0000000..a2888b6
--- /dev/null
+++ b/05_KnowingPlayer/Painter1/Painter.js
@@ -0,0 +1,62 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Game.mousePosition = { x : evt.pageX, y : evt.pageY };
+}
+
+var Game = {
+ canvas : undefined,
+ canvasContext : undefined,
+ backgroundSprite : undefined,
+ cannonBarrelSprite : undefined,
+ mousePosition : { x : 0, y : 0 },
+ cannonPosition : { x : 72, y : 405 },
+ cannonOrigin : { x : 34, y : 34 },
+ cannonRotation : 0
+};
+
+Game.clearCanvas = function () {
+ Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);
+};
+
+Game.drawImage = function (sprite, position, rotation, origin) {
+ Game.canvasContext.save();
+ Game.canvasContext.translate(position.x, position.y);
+ Game.canvasContext.rotate(rotation);
+ Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height,
+ -origin.x, -origin.y, sprite.width, sprite.height);
+ Game.canvasContext.restore();
+};
+
+Game.start = function () {
+ Game.canvas = document.getElementById("myCanvas");
+ Game.canvasContext = Game.canvas.getContext('2d');
+
+ document.onmousemove = handleMouseMove;
+
+ Game.backgroundSprite = new Image();
+ Game.backgroundSprite.src = "spr_background.jpg";
+ Game.cannonBarrelSprite = new Image();
+ Game.cannonBarrelSprite.src = "spr_cannon_barrel.png";
+ window.setTimeout(Game.mainLoop, 500);
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.mainLoop = function() {
+ Game.update();
+ Game.draw();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+ var opposite = Game.mousePosition.y - Game.cannonPosition.y;
+ var adjacent = Game.mousePosition.x - Game.cannonPosition.x;
+ Game.cannonRotation = Math.atan2(opposite, adjacent);
+};
+
+Game.draw = function () {
+ Game.clearCanvas();
+ Game.drawImage(Game.backgroundSprite, { x : 0, y : 0 }, 0, { x : 0, y : 0 });
+ Game.drawImage(Game.cannonBarrelSprite, Game.cannonPosition, Game.cannonRotation, Game.cannonOrigin);
+};
\ No newline at end of file
diff --git a/05_KnowingPlayer/Painter1/game-layout.css b/05_KnowingPlayer/Painter1/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/05_KnowingPlayer/Painter1/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/05_KnowingPlayer/Painter1/spr_background.jpg b/05_KnowingPlayer/Painter1/spr_background.jpg
new file mode 100644
index 0000000..9b7cb5f
Binary files /dev/null and b/05_KnowingPlayer/Painter1/spr_background.jpg differ
diff --git a/05_KnowingPlayer/Painter1/spr_cannon_barrel.png b/05_KnowingPlayer/Painter1/spr_cannon_barrel.png
new file mode 100644
index 0000000..a1c43aa
Binary files /dev/null and b/05_KnowingPlayer/Painter1/spr_cannon_barrel.png differ
diff --git a/06_ReactingInput/Painter2/Painter.html b/06_ReactingInput/Painter2/Painter.html
new file mode 100644
index 0000000..cfb32c2
--- /dev/null
+++ b/06_ReactingInput/Painter2/Painter.html
@@ -0,0 +1,13 @@
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/06_ReactingInput/Painter2/Painter.js b/06_ReactingInput/Painter2/Painter.js
new file mode 100644
index 0000000..c1cad24
--- /dev/null
+++ b/06_ReactingInput/Painter2/Painter.js
@@ -0,0 +1,139 @@
+"use strict";
+
+var sprites = {};
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+function handleMouseMove(evt) {
+ Mouse.position = { x : evt.pageX, y : evt.pageY };
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = true;
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+var Keyboard = { keyDown : -1 };
+
+var Keys = {
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90
+};
+
+var Mouse = {
+ position : { x : 0, y : 0 },
+ leftDown : false
+};
+
+var Canvas2D = {
+ canvas : undefined,
+ canvasContext : undefined
+};
+
+Canvas2D.initialize = function (canvasName) {
+ Canvas2D.canvas = document.getElementById(canvasName);
+ Canvas2D.canvasContext = Canvas2D.canvas.getContext('2d');
+};
+
+Canvas2D.clear = function () {
+ Canvas2D.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+};
+
+Canvas2D.drawImage = function (sprite, position, rotation, origin) {
+ Canvas2D.canvasContext.save();
+ Canvas2D.canvasContext.translate(position.x, position.y);
+ Canvas2D.canvasContext.rotate(rotation);
+ Canvas2D.canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x, -origin.y,
+ sprite.width, sprite.height);
+ Canvas2D.canvasContext.restore();
+};
+
+var cannon = {};
+
+cannon.initialize = function() {
+ cannon.position = { x : 72, y : 405 };
+ cannon.colorPosition = { x : 55, y : 388 };
+ cannon.origin = { x : 34, y : 34 };
+ cannon.currentColor = sprites.cannon_red;
+ cannon.rotation = 0;
+};
+
+cannon.update = function () {
+ if (Keyboard.keyDown === Keys.R)
+ cannon.currentColor = sprites.cannon_red;
+ else if (Keyboard.keyDown === Keys.G)
+ cannon.currentColor = sprites.cannon_green;
+ else if (Keyboard.keyDown === Keys.B)
+ cannon.currentColor = sprites.cannon_blue;
+ if (Mouse.leftDown) {
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ cannon.rotation = Math.atan2(opposite, adjacent);
+ } else
+ cannon.rotation = 0;
+};
+
+cannon.draw = function () {
+ Canvas2D.drawImage(sprites.cannon_barrel, cannon.position, cannon.rotation, cannon.origin);
+ Canvas2D.drawImage(cannon.currentColor, cannon.colorPosition, 0, { x : 0, y : 0 });
+};
+
+var Game = {};
+
+Game.start = function () {
+ Canvas2D.initialize("myCanvas");
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+
+ var spriteFolder = "../../assets/Painter/sprites/";
+ sprites.background = new Image();
+ sprites.background.src = spriteFolder + "spr_background.jpg";
+ sprites.cannon_barrel = new Image();
+ sprites.cannon_barrel.src = spriteFolder + "spr_cannon_barrel.png";
+ sprites.cannon_red = new Image();
+ sprites.cannon_red.src = spriteFolder + "spr_cannon_red.png";
+ sprites.cannon_green = new Image();
+ sprites.cannon_green.src = spriteFolder + "spr_cannon_green.png";
+ sprites.cannon_blue = new Image();
+ sprites.cannon_blue.src = spriteFolder + "spr_cannon_blue.png";
+
+ cannon.initialize();
+ window.setTimeout(Game.mainLoop, 500);
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.mainLoop = function() {
+ Game.update();
+ Game.draw();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+ cannon.update();
+};
+
+Game.draw = function () {
+ Canvas2D.clear();
+ Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 });
+ cannon.draw();
+};
\ No newline at end of file
diff --git a/06_ReactingInput/Painter2/game-layout.css b/06_ReactingInput/Painter2/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/06_ReactingInput/Painter2/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/06_ReactingInput/Painter2a/Painter.html b/06_ReactingInput/Painter2a/Painter.html
new file mode 100644
index 0000000..cfb32c2
--- /dev/null
+++ b/06_ReactingInput/Painter2a/Painter.html
@@ -0,0 +1,13 @@
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/06_ReactingInput/Painter2a/Painter.js b/06_ReactingInput/Painter2a/Painter.js
new file mode 100644
index 0000000..7e22ae2
--- /dev/null
+++ b/06_ReactingInput/Painter2a/Painter.js
@@ -0,0 +1,152 @@
+"use strict";
+
+var sprites = {};
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+function handleMouseMove(evt) {
+ Mouse.position = { x : evt.pageX, y : evt.pageY };
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+var Keyboard = { keyDown : -1 };
+
+var Keys = {
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90
+};
+
+var Mouse = {
+ position : { x : 0, y : 0 },
+ leftDown : false,
+ leftPressed : false
+};
+
+Mouse.reset = function() {
+ Mouse.leftPressed = false;
+};
+
+var Canvas2D = {
+ canvas : undefined,
+ canvasContext : undefined
+};
+
+Canvas2D.initialize = function (canvasName) {
+ Canvas2D.canvas = document.getElementById(canvasName);
+ Canvas2D.canvasContext = Canvas2D.canvas.getContext('2d');
+};
+
+Canvas2D.clear = function () {
+ Canvas2D.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+};
+
+Canvas2D.drawImage = function (sprite, position, rotation, origin) {
+ Canvas2D.canvasContext.save();
+ Canvas2D.canvasContext.translate(position.x, position.y);
+ Canvas2D.canvasContext.rotate(rotation);
+ Canvas2D.canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x, -origin.y,
+ sprite.width, sprite.height);
+ Canvas2D.canvasContext.restore();
+};
+
+var cannon = {};
+
+cannon.initialize = function() {
+ cannon.position = { x : 72, y : 405 };
+ cannon.colorPosition = { x : 55, y : 388 };
+ cannon.origin = { x : 34, y : 34 };
+ cannon.currentColor = sprites.cannon_red;
+ cannon.rotation = 0;
+ cannon.calculateAngle = false;
+};
+
+cannon.update = function () {
+ if (Keyboard.keyDown === Keys.R)
+ cannon.currentColor = sprites.cannon_red;
+ else if (Keyboard.keyDown === Keys.G)
+ cannon.currentColor = sprites.cannon_green;
+ else if (Keyboard.keyDown === Keys.B)
+ cannon.currentColor = sprites.cannon_blue;
+ if (Mouse.leftPressed)
+ cannon.calculateAngle = !cannon.calculateAngle;
+
+ if (cannon.calculateAngle) {
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ cannon.rotation = Math.atan2(opposite, adjacent);
+ } else
+ cannon.rotation = 0;
+};
+
+cannon.draw = function () {
+ Canvas2D.drawImage(sprites.cannon_barrel, cannon.position, cannon.rotation, cannon.origin);
+ Canvas2D.drawImage(cannon.currentColor, cannon.colorPosition, 0, { x : 0, y : 0 });
+};
+
+var Game = {};
+
+Game.start = function () {
+ Canvas2D.initialize("myCanvas");
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+
+ var spriteFolder = "../../assets/Painter/sprites/";
+ sprites.background = new Image();
+ sprites.background.src = spriteFolder + "spr_background.jpg";
+ sprites.cannon_barrel = new Image();
+ sprites.cannon_barrel.src = spriteFolder + "spr_cannon_barrel.png";
+ sprites.cannon_red = new Image();
+ sprites.cannon_red.src = spriteFolder + "spr_cannon_red.png";
+ sprites.cannon_green = new Image();
+ sprites.cannon_green.src = spriteFolder + "spr_cannon_green.png";
+ sprites.cannon_blue = new Image();
+ sprites.cannon_blue.src = spriteFolder + "spr_cannon_blue.png";
+
+ cannon.initialize();
+ window.setTimeout(Game.mainLoop, 500);
+};
+
+document.addEventListener( 'DOMContentLoaded', Game.start);
+
+Game.mainLoop = function() {
+ Game.update();
+ Game.draw();
+ Mouse.reset();
+ window.setTimeout(Game.mainLoop, 1000 / 60);
+};
+
+Game.update = function () {
+ cannon.update();
+};
+
+Game.draw = function () {
+ Canvas2D.clear();
+ Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 });
+ cannon.draw();
+};
\ No newline at end of file
diff --git a/06_ReactingInput/Painter2a/game-layout.css b/06_ReactingInput/Painter2a/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/06_ReactingInput/Painter2a/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/07_BasicGameObjects/LAB.min.js b/07_BasicGameObjects/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/07_BasicGameObjects/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter3/Cannon.js b/07_BasicGameObjects/Painter3/Cannon.js
new file mode 100644
index 0000000..bc5586a
--- /dev/null
+++ b/07_BasicGameObjects/Painter3/Cannon.js
@@ -0,0 +1,28 @@
+"use strict";
+
+var cannon = {};
+
+cannon.initialize = function() {
+ cannon.position = { x : 72, y : 405 };
+ cannon.colorPosition = { x : 55, y : 388 };
+ cannon.origin = { x : 34, y : 34 };
+ cannon.currentColor = sprites.cannon_red;
+ cannon.rotation = 0;
+};
+
+cannon.handleInput = function (delta) {
+ if (Keyboard.keyDown === Keys.R)
+ cannon.currentColor = sprites.cannon_red;
+ else if (Keyboard.keyDown === Keys.G)
+ cannon.currentColor = sprites.cannon_green;
+ else if (Keyboard.keyDown === Keys.B)
+ cannon.currentColor = sprites.cannon_blue;
+ var opposite = Mouse.position.y - cannon.position.y;
+ var adjacent = Mouse.position.x - cannon.position.x;
+ cannon.rotation = Math.atan2(opposite, adjacent);
+};
+
+cannon.draw = function () {
+ Canvas2D.drawImage(sprites.cannon_barrel, cannon.position, cannon.rotation, cannon.origin);
+ Canvas2D.drawImage(cannon.currentColor, cannon.colorPosition, 0, { x : 0, y : 0 });
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter3/Canvas2D.js b/07_BasicGameObjects/Painter3/Canvas2D.js
new file mode 100644
index 0000000..a654973
--- /dev/null
+++ b/07_BasicGameObjects/Painter3/Canvas2D.js
@@ -0,0 +1,26 @@
+"use strict";
+
+var Canvas2D = {
+ canvas : undefined,
+ canvasContext : undefined
+};
+
+Canvas2D.initialize = function (canvasName) {
+ Canvas2D.canvas = document.getElementById(canvasName);
+ Canvas2D.canvasContext = Canvas2D.canvas.getContext('2d');
+};
+
+Canvas2D.clear = function () {
+ Canvas2D.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+};
+
+Canvas2D.drawImage = function (sprite, position, rotation, origin) {
+ Canvas2D.canvasContext.save();
+ Canvas2D.canvasContext.translate(position.x, position.y);
+ Canvas2D.canvasContext.rotate(rotation);
+ Canvas2D.canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x, -origin.y,
+ sprite.width, sprite.height);
+ Canvas2D.canvasContext.restore();
+};
diff --git a/07_BasicGameObjects/Painter3/Painter.html b/07_BasicGameObjects/Painter3/Painter.html
new file mode 100644
index 0000000..d6a75ab
--- /dev/null
+++ b/07_BasicGameObjects/Painter3/Painter.html
@@ -0,0 +1,28 @@
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter3/Painter.js b/07_BasicGameObjects/Painter3/Painter.js
new file mode 100644
index 0000000..7c6f6da
--- /dev/null
+++ b/07_BasicGameObjects/Painter3/Painter.js
@@ -0,0 +1,78 @@
+"use strict";
+
+var sprites = {};
+
+window.requestAnimationFrame = window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+
+var Game = {
+ spritesStillLoading : 0
+};
+
+Game.start = function (canvasName) {
+ Canvas2D.initialize(canvasName);
+ Keyboard.initialize();
+ Mouse.initialize();
+ Game.loadAssets();
+ Game.assetLoadingLoop();
+};
+
+Game.initialize = function() {
+ cannon.initialize();
+};
+
+Game.handleInput = function () {
+ cannon.handleInput();
+};
+
+Game.update = function () {
+};
+
+Game.draw = function () {
+ Canvas2D.clear();
+ Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 });
+ cannon.draw();
+};
+
+Game.loadAssets = function () {
+ var spriteFolder = "../../assets/Painter/sprites/";
+
+ sprites.background = Game.loadSprite(spriteFolder + "spr_background.jpg");
+ sprites.cannon_barrel = Game.loadSprite(spriteFolder + "spr_cannon_barrel.png");
+ sprites.cannon_red = Game.loadSprite(spriteFolder + "spr_cannon_red.png");
+ sprites.cannon_green = Game.loadSprite(spriteFolder + "spr_cannon_green.png");
+ sprites.cannon_blue = Game.loadSprite(spriteFolder + "spr_cannon_blue.png");
+};
+
+Game.loadSprite = function (imageName) {
+ var image = new Image();
+ image.src = imageName;
+ Game.spritesStillLoading += 1;
+ image.onload = function () {
+ Game.spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game.assetLoadingLoop = function () {
+ if (Game.spritesStillLoading > 0)
+ window.requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ Game.mainLoop();
+ }
+};
+
+Game.mainLoop = function () {
+ Game.handleInput();
+ Game.update();
+ Game.draw();
+ Mouse.reset();
+ window.requestAnimationFrame(Game.mainLoop);
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter3/game-layout.css b/07_BasicGameObjects/Painter3/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/07_BasicGameObjects/Painter3/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter3/input/Keyboard.js b/07_BasicGameObjects/Painter3/input/Keyboard.js
new file mode 100644
index 0000000..dd340df
--- /dev/null
+++ b/07_BasicGameObjects/Painter3/input/Keyboard.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+var Keyboard = { keyDown : -1 };
+
+Keyboard.initialize = function () {
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+};
diff --git a/07_BasicGameObjects/Painter3/input/Mouse.js b/07_BasicGameObjects/Painter3/input/Mouse.js
new file mode 100644
index 0000000..896f241
--- /dev/null
+++ b/07_BasicGameObjects/Painter3/input/Mouse.js
@@ -0,0 +1,34 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse.position = { x : evt.pageX, y : evt.pageY };
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+var Mouse = {
+ position : { x : 0, y : 0 },
+ leftDown : false,
+ leftPressed : false
+};
+
+Mouse.initialize = function () {
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+};
+
+Mouse.reset = function() {
+ Mouse.leftPressed = false;
+};
diff --git a/07_BasicGameObjects/Painter3/system/Keys.js b/07_BasicGameObjects/Painter3/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/07_BasicGameObjects/Painter3/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/Ball.js b/07_BasicGameObjects/Painter4/Ball.js
new file mode 100644
index 0000000..64ebb9c
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/Ball.js
@@ -0,0 +1,53 @@
+"use strict";
+
+var ball = {
+};
+
+ball.initialize = function() {
+ ball.position = { x : 0, y : 0 };
+ ball.velocity = { x : 0, y : 0 };
+ ball.origin = { x : 0, y : 0 };
+ ball.currentColor = sprites.ball_red;
+ ball.shooting = false;
+};
+
+ball.handleInput = function (delta) {
+ if (Mouse.leftPressed && !ball.shooting) {
+ ball.shooting = true;
+ ball.velocity.x = (Mouse.position.x - ball.position.x) * 1.2;
+ ball.velocity.y = (Mouse.position.y - ball.position.y) * 1.2;
+ }
+};
+
+ball.update = function (delta) {
+ if (ball.shooting) {
+ ball.velocity.x *= 0.99;
+ ball.velocity.y += 6;
+ ball.position.x += ball.velocity.x * delta;
+ ball.position.y += ball.velocity.y * delta;
+ }
+ else {
+ if (cannon.currentColor === sprites.cannon_red)
+ ball.currentColor = sprites.ball_red;
+ else if (cannon.currentColor === sprites.cannon_green)
+ ball.currentColor = sprites.ball_green;
+ else
+ ball.currentColor = sprites.ball_blue;
+ ball.position = cannon.ballPosition();
+ ball.position.x -= ball.currentColor.width / 2;
+ ball.position.y -= ball.currentColor.height / 2;
+ }
+ if (painterGameWorld.isOutsideWorld(ball.position))
+ ball.reset();
+};
+
+ball.reset = function () {
+ ball.position = { x : 0, y : 0 };
+ ball.shooting = false;
+};
+
+ball.draw = function () {
+ if (!ball.shooting)
+ return;
+ Canvas2D.drawImage(ball.currentColor, ball.position, 0, ball.origin);
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/Cannon.js b/07_BasicGameObjects/Painter4/Cannon.js
new file mode 100644
index 0000000..178d25f
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/Cannon.js
@@ -0,0 +1,38 @@
+"use strict";
+
+var cannon = {
+};
+
+cannon.initialize = function() {
+ cannon.position = { x : 72, y : 405 };
+ cannon.colorPosition = { x : 55, y : 388 };
+ cannon.origin = { x : 34, y : 34 };
+ cannon.currentColor = sprites.cannon_red;
+ cannon.rotation = 0;
+};
+
+cannon.handleInput = function (delta) {
+ if (Keyboard.keyDown === Keys.R)
+ cannon.currentColor = sprites.cannon_red;
+ else if (Keyboard.keyDown === Keys.G)
+ cannon.currentColor = sprites.cannon_green;
+ else if (Keyboard.keyDown === Keys.B)
+ cannon.currentColor = sprites.cannon_blue;
+ var opposite = Mouse.position.y - cannon.position.y;
+ var adjacent = Mouse.position.x - cannon.position.x;
+ cannon.rotation = Math.atan2(opposite, adjacent);
+};
+
+cannon.update = function (delta) {
+};
+
+cannon.draw = function () {
+ Canvas2D.drawImage(sprites.cannon_barrel, cannon.position, cannon.rotation, cannon.origin);
+ Canvas2D.drawImage(cannon.currentColor, cannon.colorPosition, 0, { x : 0, y : 0 });
+};
+
+cannon.ballPosition = function() {
+ var opposite = Math.sin(cannon.rotation) * sprites.cannon_barrel.width * 0.6;
+ var adjacent = Math.cos(cannon.rotation) * sprites.cannon_barrel.width * 0.6;
+ return { x : cannon.position.x + adjacent, y : cannon.position.y + opposite };
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/Canvas2D.js b/07_BasicGameObjects/Painter4/Canvas2D.js
new file mode 100644
index 0000000..925ee25
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/Canvas2D.js
@@ -0,0 +1,26 @@
+"use strict";
+
+var Canvas2D = {
+ canvas : undefined,
+ canvasContext : undefined
+};
+
+Canvas2D.initialize = function (canvasName) {
+ Canvas2D.canvas = document.getElementById(canvasName);
+ Canvas2D.canvasContext = Canvas2D.canvas.getContext('2d');
+};
+
+Canvas2D.clear = function () {
+ Canvas2D.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+};
+
+Canvas2D.drawImage = function (sprite, position, rotation, origin) {
+ Canvas2D.canvasContext.save();
+ Canvas2D.canvasContext.translate(position.x, position.y);
+ Canvas2D.canvasContext.rotate(rotation);
+ Canvas2D.canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x, -origin.y,
+ sprite.width, sprite.height);
+ Canvas2D.canvasContext.restore();
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/Game.js b/07_BasicGameObjects/Painter4/Game.js
new file mode 100644
index 0000000..0ba3569
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/Game.js
@@ -0,0 +1,59 @@
+"use strict";
+
+window.requestAnimationFrame = window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+
+var Game = {
+ spritesStillLoading : 0,
+ gameWorld : undefined
+}
+
+Game.start = function (canvasName, x, y) {
+ Canvas2D.initialize(canvasName);
+ Game.size = { x : x, y : y };
+ Keyboard.initialize();
+ Mouse.initialize();
+ Game.loadAssets();
+ Game.assetLoadingLoop();
+};
+
+Game.loadAssets = function () {
+};
+
+Game.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this.spritesStillLoading += 1;
+ image.onload = function () {
+ Game.spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game.assetLoadingLoop = function () {
+ if (!Game.spritesStillLoading > 0)
+ window.requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ window.requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game.mainLoop = function () {
+ var delta = 1 / 60;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+ Mouse.reset();
+ requestAnimationFrame(Game.mainLoop);
+};
+
diff --git a/07_BasicGameObjects/Painter4/Painter.html b/07_BasicGameObjects/Painter4/Painter.html
new file mode 100644
index 0000000..d11ae25
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/Painter.html
@@ -0,0 +1,30 @@
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/Painter.js b/07_BasicGameObjects/Painter4/Painter.js
new file mode 100644
index 0000000..a2b34ba
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/Painter.js
@@ -0,0 +1,26 @@
+"use strict";
+
+var sprites = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/Painter/sprites/" + sprite);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.cannon_barrel = loadSprite("spr_cannon_barrel.png");
+ sprites.cannon_red = loadSprite("spr_cannon_red.png");
+ sprites.cannon_green = loadSprite("spr_cannon_green.png");
+ sprites.cannon_blue = loadSprite("spr_cannon_blue.png");
+ sprites.ball_red = loadSprite("spr_ball_red.png");
+ sprites.ball_green = loadSprite("spr_ball_green.png");
+ sprites.ball_blue = loadSprite("spr_ball_blue.png");
+};
+
+Game.initialize = function () {
+
+ console.log("Creating game world");
+ cannon.initialize();
+ ball.initialize();
+ Game.gameWorld = painterGameWorld;
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/PainterGameWorld.js b/07_BasicGameObjects/Painter4/PainterGameWorld.js
new file mode 100644
index 0000000..c9f1351
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/PainterGameWorld.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var painterGameWorld = {
+};
+
+painterGameWorld.handleInput = function (delta) {
+ ball.handleInput(delta);
+ cannon.handleInput(delta);
+};
+
+painterGameWorld.update = function (delta) {
+ ball.update(delta);
+ cannon.update(delta);
+};
+
+painterGameWorld.draw = function () {
+ Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 });
+ ball.draw();
+ cannon.draw();
+};
+
+painterGameWorld.reset = function () {
+ ball.reset();
+ cannon.reset();
+};
+
+painterGameWorld.isOutsideWorld = function (position) {
+ return position.x < 0 || position.x > Game.size.x || position.y > Game.size.y;
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/game-layout.css b/07_BasicGameObjects/Painter4/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/input/Keyboard.js b/07_BasicGameObjects/Painter4/input/Keyboard.js
new file mode 100644
index 0000000..554d72e
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/input/Keyboard.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+var Keyboard = { keyDown : -1 };
+
+Keyboard.initialize = function () {
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/input/Mouse.js b/07_BasicGameObjects/Painter4/input/Mouse.js
new file mode 100644
index 0000000..44895e8
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/input/Mouse.js
@@ -0,0 +1,34 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse.position = { x : evt.pageX, y : evt.pageY };
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+var Mouse = {
+ position : { x : 0, y : 0 },
+ leftDown : false,
+ leftPressed : false
+};
+
+Mouse.initialize = function () {
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+};
+
+Mouse.reset = function() {
+ Mouse.leftPressed = false;
+};
\ No newline at end of file
diff --git a/07_BasicGameObjects/Painter4/system/Keys.js b/07_BasicGameObjects/Painter4/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/07_BasicGameObjects/Painter4/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/LAB.min.js b/08_GameObjectTypes/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/08_GameObjectTypes/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/Ball.js b/08_GameObjectTypes/Painter5/Ball.js
new file mode 100644
index 0000000..f8d0c3b
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/Ball.js
@@ -0,0 +1,50 @@
+"use strict";
+
+function Ball() {
+ this.position = { x : 0, y : 0 };
+ this.velocity = { x : 0, y : 0 };
+ this.origin = { x : 0, y : 0 };
+ this.currentColor = sprites.ball_red;
+ this.shooting = false;
+}
+
+Ball.prototype.handleInput = function (delta) {
+ if (Mouse.leftPressed && !this.shooting) {
+ this.shooting = true;
+ this.velocity.x = (Mouse.position.x - this.position.x) * 1.2;
+ this.velocity.y = (Mouse.position.y - this.position.y) * 1.2;
+ }
+};
+
+Ball.prototype.update = function (delta) {
+ if (this.shooting) {
+ this.velocity.x *= 0.99;
+ this.velocity.y += 6;
+ this.position.x += this.velocity.x * delta;
+ this.position.y += this.velocity.y * delta;
+ }
+ else {
+ if (Game.gameWorld.cannon.currentColor === sprites.cannon_red)
+ this.currentColor = sprites.ball_red;
+ else if (Game.gameWorld.cannon.currentColor === sprites.cannon_green)
+ this.currentColor = sprites.ball_green;
+ else
+ this.currentColor = sprites.ball_blue;
+ this.position = Game.gameWorld.cannon.ballPosition();
+ this.position.x -= this.currentColor.width / 2;
+ this.position.y -= this.currentColor.height / 2;
+ }
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.reset();
+};
+
+Ball.prototype.reset = function () {
+ this.position = { x : 0, y : 0 };
+ this.shooting = false;
+};
+
+Ball.prototype.draw = function () {
+ if (!this.shooting)
+ return;
+ Canvas2D.drawImage(this.currentColor, this.position, 0, this.origin);
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/Cannon.js b/08_GameObjectTypes/Painter5/Cannon.js
new file mode 100644
index 0000000..4605f3b
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/Cannon.js
@@ -0,0 +1,39 @@
+"use strict";
+
+function Cannon() {
+ this.position = { x : 72, y : 405 };
+ this.colorPosition = { x : 55, y : 388 };
+ this.origin = { x : 34, y : 34 };
+ this.currentColor = sprites.cannon_red;
+ this.rotation = 0;
+}
+
+Cannon.prototype.reset = function () {
+ this.position = new Vector2(72, 405);
+};
+
+Cannon.prototype.handleInput = function (delta) {
+ if (Keyboard.keyDown === Keys.R)
+ this.currentColor = sprites.cannon_red;
+ else if (Keyboard.keyDown === Keys.G)
+ this.currentColor = sprites.cannon_green;
+ else if (Keyboard.keyDown === Keys.B)
+ this.currentColor = sprites.cannon_blue;
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ this.rotation = Math.atan2(opposite, adjacent);
+};
+
+Cannon.prototype.update = function (delta) {
+};
+
+Cannon.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.cannon_barrel, this.position, this.rotation, this.origin);
+ Canvas2D.drawImage(this.currentColor, this.colorPosition, 0, { x : 0, y : 0 });
+};
+
+Cannon.prototype.ballPosition = function() {
+ var opposite = Math.sin(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ var adjacent = Math.cos(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ return { x : this.position.x + adjacent, y : this.position.y + opposite };
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/Canvas2D.js b/08_GameObjectTypes/Painter5/Canvas2D.js
new file mode 100644
index 0000000..01e244b
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/Canvas2D.js
@@ -0,0 +1,28 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this.canvas = undefined;
+ this.canvasContext = undefined;
+}
+
+Canvas2D_Singleton.prototype.initialize = function (canvasName) {
+ this.canvas = document.getElementById(canvasName);
+ this.canvasContext = this.canvas.getContext('2d');
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, origin) {
+ this.canvasContext.save();
+ this.canvasContext.translate(position.x, position.y);
+ this.canvasContext.rotate(rotation);
+ this.canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x, -origin.y,
+ sprite.width, sprite.height);
+ this.canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/Game.js b/08_GameObjectTypes/Painter5/Game.js
new file mode 100644
index 0000000..4ca17a3
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/Game.js
@@ -0,0 +1,66 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this.size = undefined;
+ this.spritesStillLoading = 0;
+ this.gameWorld = undefined;
+}
+
+Game_Singleton.prototype.start = function (canvasName, x, y) {
+ this.size = { x : x, y : y };
+ Canvas2D.initialize(canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+ this.gameWorld = new PainterGameWorld();
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this.spritesStillLoading += 1;
+ image.onload = function () {
+ Game.spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ if (!this.spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+ Mouse.reset();
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
+
diff --git a/08_GameObjectTypes/Painter5/PaintCan.js b/08_GameObjectTypes/Painter5/PaintCan.js
new file mode 100644
index 0000000..08cb929
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/PaintCan.js
@@ -0,0 +1,52 @@
+"use strict";
+
+function PaintCan(positionOffset) {
+ this.currentColor = sprites.can_red;
+ this.velocity = { x : 0, y : 0 };
+ this.position = { x : 0, y : 0 };
+ this.origin = { x : 0, y : 0 };
+ this.positionOffset = positionOffset;
+ this.reset();
+}
+
+PaintCan.prototype.reset = function () {
+ this.moveToTop();
+ this.minVelocity = 30;
+};
+
+PaintCan.prototype.moveToTop = function () {
+ this.position = { x : this.positionOffset, y : -200 };
+ this.velocity = { x : 0, y : 0 };
+};
+
+PaintCan.prototype.update = function (delta) {
+ this.position.x += this.velocity.x * delta;
+ this.position.y += this.velocity.y * delta;
+
+ if (this.velocity.y === 0 && Math.random() < 0.01) {
+ this.velocity = this.calculateRandomVelocity();
+ this.currentColor = this.calculateRandomColor();
+ }
+
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.moveToTop();
+ this.minVelocity += 0.01;
+};
+
+PaintCan.prototype.draw = function () {
+ Canvas2D.drawImage(this.currentColor, this.position, 0, this.origin);
+};
+
+PaintCan.prototype.calculateRandomVelocity = function () {
+ return { x : 0, y : Math.random() * 30 + this.minVelocity };
+};
+
+PaintCan.prototype.calculateRandomColor = function () {
+ var randomval = Math.floor(Math.random() * 3);
+ if (randomval == 0)
+ return sprites.can_red;
+ else if (randomval == 1)
+ return sprites.can_green;
+ else
+ return sprites.can_blue;
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/Painter.html b/08_GameObjectTypes/Painter5/Painter.html
new file mode 100644
index 0000000..bc1620f
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/Painter.html
@@ -0,0 +1,32 @@
+
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/Painter.js b/08_GameObjectTypes/Painter5/Painter.js
new file mode 100644
index 0000000..990b924
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/Painter.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/Painter/sprites/" + sprite);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.cannon_barrel = loadSprite("spr_cannon_barrel.png");
+ sprites.cannon_red = loadSprite("spr_cannon_red.png");
+ sprites.cannon_green = loadSprite("spr_cannon_green.png");
+ sprites.cannon_blue = loadSprite("spr_cannon_blue.png");
+ sprites.ball_red = loadSprite("spr_ball_red.png");
+ sprites.ball_green = loadSprite("spr_ball_green.png");
+ sprites.ball_blue = loadSprite("spr_ball_blue.png");
+ sprites.can_red = loadSprite("spr_can_red.png");
+ sprites.can_green = loadSprite("spr_can_green.png");
+ sprites.can_blue = loadSprite("spr_can_blue.png");
+ sprites.lives = loadSprite("spr_lives.png");
+ sprites.gameover = loadSprite("spr_gameover_click.jpg");
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/PainterGameWorld.js b/08_GameObjectTypes/Painter5/PainterGameWorld.js
new file mode 100644
index 0000000..e9cad3b
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/PainterGameWorld.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function PainterGameWorld() {
+ this.cannon = new Cannon();
+ this.ball = new Ball();
+
+ this.can1 = new PaintCan(450);
+ this.can2 = new PaintCan(575);
+ this.can3 = new PaintCan(700);
+}
+
+PainterGameWorld.prototype.handleInput = function (delta) {
+ this.ball.handleInput(delta);
+ this.cannon.handleInput(delta);
+};
+
+PainterGameWorld.prototype.update = function (delta) {
+ this.ball.update(delta);
+ this.cannon.update(delta);
+ this.can1.update(delta);
+ this.can2.update(delta);
+ this.can3.update(delta);
+};
+
+PainterGameWorld.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 });
+
+ this.ball.draw();
+ this.cannon.draw();
+ this.can1.draw();
+ this.can2.draw();
+ this.can3.draw();
+};
+
+PainterGameWorld.prototype.reset = function () {
+ this.ball.reset();
+ this.cannon.reset();
+ this.can1.reset();
+ this.can2.reset();
+ this.can3.reset();
+};
+
+PainterGameWorld.prototype.isOutsideWorld = function (position) {
+ return position.x < 0 || position.x > Game.size.x || position.y > Game.size.y;
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/game-layout.css b/08_GameObjectTypes/Painter5/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter5/input/Keyboard.js b/08_GameObjectTypes/Painter5/input/Keyboard.js
new file mode 100644
index 0000000..2bff5d9
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/input/Keyboard.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+function Keyboard_Singleton() {
+ this.keyDown = -1;
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/08_GameObjectTypes/Painter5/input/Mouse.js b/08_GameObjectTypes/Painter5/input/Mouse.js
new file mode 100644
index 0000000..e789a14
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/input/Mouse.js
@@ -0,0 +1,33 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse.position = { x : evt.pageX, y : evt.pageY };
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+function Mouse_Singleton() {
+ this.position = { x : 0, y : 0 };
+ this.leftDown = false;
+ this.leftPressed = false;
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Mouse_Singleton.prototype.reset = function () {
+ this.leftPressed = false;
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/08_GameObjectTypes/Painter5/system/Keys.js b/08_GameObjectTypes/Painter5/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/08_GameObjectTypes/Painter5/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/Ball.js b/08_GameObjectTypes/Painter6/Ball.js
new file mode 100644
index 0000000..28fc91d
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/Ball.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function Ball() {
+ this.position = new Vector2();
+ this.velocity = new Vector2();
+ this.origin = new Vector2();
+ this.currentColor = sprites.ball_red;
+ this.shooting = false;
+}
+
+Ball.prototype.handleInput = function (delta) {
+ if (Mouse.leftPressed && !this.shooting) {
+ this.shooting = true;
+ this.velocity = Mouse.position.subtract(this.position).multiplyWith(1.2);
+ }
+};
+
+Ball.prototype.update = function (delta) {
+ if (this.shooting) {
+ this.velocity.x *= 0.99;
+ this.velocity.y += 6;
+ this.position.addTo(this.velocity.multiply(delta));
+ }
+ else {
+ if (Game.gameWorld.cannon.currentColor === sprites.cannon_red)
+ this.currentColor = sprites.ball_red;
+ else if (Game.gameWorld.cannon.currentColor === sprites.cannon_green)
+ this.currentColor = sprites.ball_green;
+ else
+ this.currentColor = sprites.ball_blue;
+ var center = new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ this.position = Game.gameWorld.cannon.ballPosition().subtractFrom(center);
+
+ }
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.reset();
+};
+
+Ball.prototype.reset = function () {
+ this.position = new Vector2();
+ this.shooting = false;
+};
+
+Ball.prototype.draw = function () {
+ if (!this.shooting)
+ return;
+ Canvas2D.drawImage(this.currentColor, this.position, 0, this.origin);
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/Cannon.js b/08_GameObjectTypes/Painter6/Cannon.js
new file mode 100644
index 0000000..029c098
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/Cannon.js
@@ -0,0 +1,39 @@
+"use strict";
+
+function Cannon() {
+ this.position = new Vector2(72, 405);
+ this.colorPosition = new Vector2(55, 388);
+ this.origin = new Vector2(34, 34);
+ this.currentColor = sprites.cannon_red;
+ this.rotation = 0;
+}
+
+Cannon.prototype.reset = function () {
+ this.position = new Vector2(72, 405);
+};
+
+Cannon.prototype.handleInput = function (delta) {
+ if (Keyboard.keyDown === Keys.R)
+ this.currentColor = sprites.cannon_red;
+ else if (Keyboard.keyDown === Keys.G)
+ this.currentColor = sprites.cannon_green;
+ else if (Keyboard.keyDown === Keys.B)
+ this.currentColor = sprites.cannon_blue;
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ this.rotation = Math.atan2(opposite, adjacent);
+};
+
+Cannon.prototype.update = function (delta) {
+};
+
+Cannon.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.cannon_barrel, this.position, this.rotation, this.origin);
+ Canvas2D.drawImage(this.currentColor, this.colorPosition, 0, new Vector2());
+};
+
+Cannon.prototype.ballPosition = function() {
+ var opposite = Math.sin(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ var adjacent = Math.cos(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ return new Vector2(this.position.x + adjacent, this.position.y + opposite);
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/Canvas2D.js b/08_GameObjectTypes/Painter6/Canvas2D.js
new file mode 100644
index 0000000..01e244b
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/Canvas2D.js
@@ -0,0 +1,28 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this.canvas = undefined;
+ this.canvasContext = undefined;
+}
+
+Canvas2D_Singleton.prototype.initialize = function (canvasName) {
+ this.canvas = document.getElementById(canvasName);
+ this.canvasContext = this.canvas.getContext('2d');
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, origin) {
+ this.canvasContext.save();
+ this.canvasContext.translate(position.x, position.y);
+ this.canvasContext.rotate(rotation);
+ this.canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x, -origin.y,
+ sprite.width, sprite.height);
+ this.canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/Game.js b/08_GameObjectTypes/Painter6/Game.js
new file mode 100644
index 0000000..af623b4
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/Game.js
@@ -0,0 +1,65 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this.size = undefined;
+ this.spritesStillLoading = 0;
+ this.gameWorld = undefined;
+}
+
+Game_Singleton.prototype.start = function (canvasName, x, y) {
+ this.size = { x : x, y : y };
+ Canvas2D.initialize(canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this.spritesStillLoading += 1;
+ image.onload = function () {
+ Game.spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ if (!this.spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+ Mouse.reset();
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
+
diff --git a/08_GameObjectTypes/Painter6/PaintCan.js b/08_GameObjectTypes/Painter6/PaintCan.js
new file mode 100644
index 0000000..59b97ef
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/PaintCan.js
@@ -0,0 +1,50 @@
+"use strict";
+
+function PaintCan(xPosition) {
+ this.currentColor = sprites.can_red;
+ this.velocity = new Vector2();
+ this.position = new Vector2(xPosition, -200);
+ this.origin = new Vector2();
+ this.reset();
+}
+
+PaintCan.prototype.reset = function () {
+ this.moveToTop();
+ this.minVelocity = 30;
+};
+
+PaintCan.prototype.moveToTop = function () {
+ this.position.y = -200;
+ this.velocity = new Vector2();
+};
+
+PaintCan.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+
+ if (this.velocity.y === 0 && Math.random() < 0.01) {
+ this.velocity = this.calculateRandomVelocity();
+ this.currentColor = this.calculateRandomColor();
+ }
+
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.moveToTop();
+ this.minVelocity += 0.01;
+};
+
+PaintCan.prototype.draw = function () {
+ Canvas2D.drawImage(this.currentColor, this.position, 0, this.origin);
+};
+
+PaintCan.prototype.calculateRandomVelocity = function () {
+ return new Vector2(0, Math.random() * 30 + this.minVelocity);
+};
+
+PaintCan.prototype.calculateRandomColor = function () {
+ var randomval = Math.floor(Math.random() * 3);
+ if (randomval == 0)
+ return sprites.can_red;
+ else if (randomval == 1)
+ return sprites.can_green;
+ else
+ return sprites.can_blue;
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/Painter.html b/08_GameObjectTypes/Painter6/Painter.html
new file mode 100644
index 0000000..5e26186
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/Painter.html
@@ -0,0 +1,33 @@
+
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/Painter.js b/08_GameObjectTypes/Painter6/Painter.js
new file mode 100644
index 0000000..f57a249
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/Painter.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/Painter/sprites/" + sprite);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.cannon_barrel = loadSprite("spr_cannon_barrel.png");
+ sprites.cannon_red = loadSprite("spr_cannon_red.png");
+ sprites.cannon_green = loadSprite("spr_cannon_green.png");
+ sprites.cannon_blue = loadSprite("spr_cannon_blue.png");
+ sprites.ball_red = loadSprite("spr_ball_red.png");
+ sprites.ball_green = loadSprite("spr_ball_green.png");
+ sprites.ball_blue = loadSprite("spr_ball_blue.png");
+ sprites.can_red = loadSprite("spr_can_red.png");
+ sprites.can_green = loadSprite("spr_can_green.png");
+ sprites.can_blue = loadSprite("spr_can_blue.png");
+ sprites.lives = loadSprite("spr_lives.png");
+ sprites.gameover = loadSprite("spr_gameover_click.jpg");
+};
+
+Game.initialize = function () {
+
+ Game.gameWorld = new PainterGameWorld();
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/PainterGameWorld.js b/08_GameObjectTypes/Painter6/PainterGameWorld.js
new file mode 100644
index 0000000..e9cad3b
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/PainterGameWorld.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function PainterGameWorld() {
+ this.cannon = new Cannon();
+ this.ball = new Ball();
+
+ this.can1 = new PaintCan(450);
+ this.can2 = new PaintCan(575);
+ this.can3 = new PaintCan(700);
+}
+
+PainterGameWorld.prototype.handleInput = function (delta) {
+ this.ball.handleInput(delta);
+ this.cannon.handleInput(delta);
+};
+
+PainterGameWorld.prototype.update = function (delta) {
+ this.ball.update(delta);
+ this.cannon.update(delta);
+ this.can1.update(delta);
+ this.can2.update(delta);
+ this.can3.update(delta);
+};
+
+PainterGameWorld.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 });
+
+ this.ball.draw();
+ this.cannon.draw();
+ this.can1.draw();
+ this.can2.draw();
+ this.can3.draw();
+};
+
+PainterGameWorld.prototype.reset = function () {
+ this.ball.reset();
+ this.cannon.reset();
+ this.can1.reset();
+ this.can2.reset();
+ this.can3.reset();
+};
+
+PainterGameWorld.prototype.isOutsideWorld = function (position) {
+ return position.x < 0 || position.x > Game.size.x || position.y > Game.size.y;
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/game-layout.css b/08_GameObjectTypes/Painter6/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/geom/Vector2.js b/08_GameObjectTypes/Painter6/geom/Vector2.js
new file mode 100644
index 0000000..ea34ef7
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/geom/Vector2.js
@@ -0,0 +1,93 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/08_GameObjectTypes/Painter6/input/Keyboard.js b/08_GameObjectTypes/Painter6/input/Keyboard.js
new file mode 100644
index 0000000..2bff5d9
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/input/Keyboard.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+function Keyboard_Singleton() {
+ this.keyDown = -1;
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/08_GameObjectTypes/Painter6/input/Mouse.js b/08_GameObjectTypes/Painter6/input/Mouse.js
new file mode 100644
index 0000000..8d4071e
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/input/Mouse.js
@@ -0,0 +1,33 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse.position = new Vector2(evt.pageX, evt.pageY);
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+function Mouse_Singleton() {
+ this.position = new Vector2();
+ this.leftDown = false;
+ this.leftPressed = false;
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Mouse_Singleton.prototype.reset = function () {
+ this.leftPressed = false;
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/08_GameObjectTypes/Painter6/system/Keys.js b/08_GameObjectTypes/Painter6/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/08_GameObjectTypes/Painter6/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/09_ColorsCollisions/LAB.min.js b/09_ColorsCollisions/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/09_ColorsCollisions/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/Ball.js b/09_ColorsCollisions/Painter7/Ball.js
new file mode 100644
index 0000000..90deb1a
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/Ball.js
@@ -0,0 +1,89 @@
+"use strict";
+
+function Ball() {
+ this.currentColor = sprites.ball_red;
+ this.velocity = Vector2.zero;
+ this.position = Vector2.zero;
+ this.origin = Vector2.zero;
+ this.shooting = false;
+}
+
+Object.defineProperty(Ball.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === sprites.ball_red)
+ return Color.red;
+ else if (this.currentColor === sprites.ball_green)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = sprites.ball_red;
+ else if (value === Color.green)
+ this.currentColor = sprites.ball_green;
+ else if (value === Color.blue)
+ this.currentColor = sprites.ball_blue;
+ }
+ });
+
+Object.defineProperty(Ball.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(Ball.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(Ball.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(Ball.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+Ball.prototype.handleInput = function (delta) {
+ if (Mouse.leftPressed && !this.shooting) {
+ this.shooting = true;
+ this.velocity = Mouse.position.subtract(this.position).multiplyWith(1.2);
+ }
+};
+
+Ball.prototype.update = function (delta) {
+ if (this.shooting) {
+ this.velocity.x *= 0.99;
+ this.velocity.y += 6;
+ this.position.addTo(this.velocity.multiply(delta));
+ }
+ else {
+ this.color = Game.gameWorld.cannon.color;
+ this.position = Game.gameWorld.cannon.ballPosition.subtractFrom(this.center);
+ }
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.reset();
+};
+
+Ball.prototype.reset = function () {
+ this.position = Vector2.zero;
+ this.shooting = false;
+};
+
+Ball.prototype.draw = function () {
+ if (!this.shooting)
+ return;
+ Canvas2D.drawImage(this.currentColor, this.position, 0, this.origin);
+};
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/Cannon.js b/09_ColorsCollisions/Painter7/Cannon.js
new file mode 100644
index 0000000..274f9c9
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/Cannon.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function Cannon() {
+ this.position = new Vector2(72, 405);
+ this.origin = new Vector2(34, 34);
+ this.currentColor = sprites.cannon_red;
+ this.rotation = 0;
+}
+
+Object.defineProperty(Cannon.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === sprites.cannon_red)
+ return Color.red;
+ else if (this.currentColor === sprites.cannon_green)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = sprites.cannon_red;
+ else if (value === Color.green)
+ this.currentColor = sprites.cannon_green;
+ else if (value === Color.blue)
+ this.currentColor = sprites.cannon_blue;
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "ballPosition",
+ {
+ get: function () {
+ var opposite = Math.sin(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ var adjacent = Math.cos(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ return new Vector2(this.position.x + adjacent, this.position.y + opposite);
+ }
+ });
+
+
+Cannon.prototype.reset = function () {
+ this.position = new Vector2(72, 405);
+};
+
+Cannon.prototype.handleInput = function (delta) {
+ if (Keyboard.keyDown === Keys.R)
+ this.color = Color.red;
+ else if (Keyboard.keyDown === Keys.G)
+ this.color = Color.green;
+ else if (Keyboard.keyDown === Keys.B)
+ this.color = Color.blue;
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ this.rotation = Math.atan2(opposite, adjacent);
+};
+
+
+Cannon.prototype.update = function (delta) {
+};
+
+Cannon.prototype.draw = function () {
+ var colorPosition = this.position.subtract(this.size.divideBy(2));
+ Canvas2D.drawImage(sprites.cannon_barrel, this.position, this.rotation, this.origin);
+ Canvas2D.drawImage(this.currentColor, colorPosition);
+};
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/Canvas2D.js b/09_ColorsCollisions/Painter7/Canvas2D.js
new file mode 100644
index 0000000..33d7c47
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/Canvas2D.js
@@ -0,0 +1,38 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ console.log("Creating Canvas2D object");
+ this._canvas = null;
+ this._canvasContext = null;
+}
+
+Canvas2D_Singleton.prototype.initialize = function (canvasName) {
+ this._canvas = document.getElementById(canvasName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ }
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, origin) {
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+
+ this._canvasContext.save();
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x, -origin.y,
+ sprite.width, sprite.height);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/Game.js b/09_ColorsCollisions/Painter7/Game.js
new file mode 100644
index 0000000..0d0d219
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/Game.js
@@ -0,0 +1,84 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ console.log("Creating game");
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this.gameWorld = null;
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Game_Singleton.prototype.start = function (canvasName, x, y) {
+ this._size = new Vector2(x, y);
+
+ Canvas2D.initialize(canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ if (!this._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+ Mouse.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
+
diff --git a/09_ColorsCollisions/Painter7/PaintCan.js b/09_ColorsCollisions/Painter7/PaintCan.js
new file mode 100644
index 0000000..19a2725
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/PaintCan.js
@@ -0,0 +1,107 @@
+"use strict";
+
+function PaintCan(xPosition) {
+ this.currentColor = sprites.can_red;
+ this.velocity = Vector2.zero;
+ this.position = new Vector2(xPosition, -200);
+ this.origin = Vector2.zero;
+ this.reset();
+}
+
+Object.defineProperty(PaintCan.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === sprites.can_red)
+ return Color.red;
+ else if (this.currentColor === sprites.can_green)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = sprites.can_red;
+ else if (value === Color.green)
+ this.currentColor = sprites.can_green;
+ else if (value === Color.blue)
+ this.currentColor = sprites.can_blue;
+ }
+ });
+
+Object.defineProperty(PaintCan.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(PaintCan.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(PaintCan.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(PaintCan.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+PaintCan.prototype.reset = function () {
+ this.moveToTop();
+ this.minVelocity = 30;
+};
+
+PaintCan.prototype.moveToTop = function () {
+ this.position.y = -200;
+ this.velocity = Vector2.zero;
+};
+
+PaintCan.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+
+ if (this.velocity.y === 0 && Math.random() < 0.01) {
+ this.velocity = this.calculateRandomVelocity();
+ this.color = this.calculateRandomColor();
+ }
+
+ // calculate the distance between this can and the ball
+ var ball = Game.gameWorld.ball;
+ var distance = ball.position.add(ball.center).subtractFrom(this.position).subtractFrom(this.center);
+
+ if (Math.abs(distance.x) < this.center.x && Math.abs(distance.y) < this.center.y) {
+ this.color = ball.color;
+ ball.reset();
+ }
+
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.moveToTop();
+ this.minVelocity += 0.01;
+};
+
+PaintCan.prototype.draw = function () {
+ Canvas2D.drawImage(this.currentColor, this.position, this.rotation, this.origin);
+};
+
+PaintCan.prototype.calculateRandomVelocity = function () {
+ return new Vector2(0, Math.random() * 30 + this.minVelocity);
+};
+
+PaintCan.prototype.calculateRandomColor = function () {
+ var randomval = Math.floor(Math.random() * 3);
+ if (randomval == 0)
+ return Color.red;
+ else if (randomval == 1)
+ return Color.green;
+ else
+ return Color.blue;
+};
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/Painter.html b/09_ColorsCollisions/Painter7/Painter.html
new file mode 100644
index 0000000..478f156
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/Painter.html
@@ -0,0 +1,34 @@
+
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/Painter.js b/09_ColorsCollisions/Painter7/Painter.js
new file mode 100644
index 0000000..f57a249
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/Painter.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/Painter/sprites/" + sprite);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.cannon_barrel = loadSprite("spr_cannon_barrel.png");
+ sprites.cannon_red = loadSprite("spr_cannon_red.png");
+ sprites.cannon_green = loadSprite("spr_cannon_green.png");
+ sprites.cannon_blue = loadSprite("spr_cannon_blue.png");
+ sprites.ball_red = loadSprite("spr_ball_red.png");
+ sprites.ball_green = loadSprite("spr_ball_green.png");
+ sprites.ball_blue = loadSprite("spr_ball_blue.png");
+ sprites.can_red = loadSprite("spr_can_red.png");
+ sprites.can_green = loadSprite("spr_can_green.png");
+ sprites.can_blue = loadSprite("spr_can_blue.png");
+ sprites.lives = loadSprite("spr_lives.png");
+ sprites.gameover = loadSprite("spr_gameover_click.jpg");
+};
+
+Game.initialize = function () {
+
+ Game.gameWorld = new PainterGameWorld();
+};
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/PainterGameWorld.js b/09_ColorsCollisions/Painter7/PainterGameWorld.js
new file mode 100644
index 0000000..0d4eadb
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/PainterGameWorld.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function PainterGameWorld() {
+ this.cannon = new Cannon();
+ this.ball = new Ball();
+
+ this.can1 = new PaintCan(450);
+ this.can2 = new PaintCan(575);
+ this.can3 = new PaintCan(700);
+}
+
+PainterGameWorld.prototype.handleInput = function (delta) {
+ this.ball.handleInput(delta);
+ this.cannon.handleInput(delta);
+};
+
+PainterGameWorld.prototype.update = function (delta) {
+ this.ball.update(delta);
+ this.cannon.update(delta);
+ this.can1.update(delta);
+ this.can2.update(delta);
+ this.can3.update(delta);
+};
+
+PainterGameWorld.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.background);
+
+ this.ball.draw();
+ this.cannon.draw();
+ this.can1.draw();
+ this.can2.draw();
+ this.can3.draw();
+};
+
+PainterGameWorld.prototype.reset = function () {
+ this.ball.reset();
+ this.cannon.reset();
+ this.can1.reset();
+ this.can2.reset();
+ this.can3.reset();
+};
+
+PainterGameWorld.prototype.isOutsideWorld = function (position) {
+ return position.x < 0 || position.x > Game.size.x || position.y > Game.size.y;
+};
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/game-layout.css b/09_ColorsCollisions/Painter7/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/geom/Vector2.js b/09_ColorsCollisions/Painter7/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/input/Keyboard.js b/09_ColorsCollisions/Painter7/input/Keyboard.js
new file mode 100644
index 0000000..2bff5d9
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/input/Keyboard.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+function Keyboard_Singleton() {
+ this.keyDown = -1;
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/09_ColorsCollisions/Painter7/input/Mouse.js b/09_ColorsCollisions/Painter7/input/Mouse.js
new file mode 100644
index 0000000..8d4071e
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/input/Mouse.js
@@ -0,0 +1,33 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse.position = new Vector2(evt.pageX, evt.pageY);
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+function Mouse_Singleton() {
+ this.position = new Vector2();
+ this.leftDown = false;
+ this.leftPressed = false;
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Mouse_Singleton.prototype.reset = function () {
+ this.leftPressed = false;
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/09_ColorsCollisions/Painter7/myGame.js b/09_ColorsCollisions/Painter7/myGame.js
new file mode 100644
index 0000000..e69de29
diff --git a/09_ColorsCollisions/Painter7/system/Color.js b/09_ColorsCollisions/Painter7/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/09_ColorsCollisions/Painter7/system/Keys.js b/09_ColorsCollisions/Painter7/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/09_ColorsCollisions/Painter7/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/10_LimitedLives/LAB.min.js b/10_LimitedLives/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/10_LimitedLives/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/Ball.js b/10_LimitedLives/Painter8/Ball.js
new file mode 100644
index 0000000..ed779d8
--- /dev/null
+++ b/10_LimitedLives/Painter8/Ball.js
@@ -0,0 +1,91 @@
+"use strict";
+
+function Ball() {
+ this.currentColor = sprites.ball_red;
+ this.velocity = Vector2.zero;
+ this.position = Vector2.zero;
+ this.origin = Vector2.zero;
+ this.rotation = 0;
+ this.shooting = false;
+ this.reset();
+}
+
+Object.defineProperty(Ball.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === sprites.ball_red)
+ return Color.red;
+ else if (this.currentColor === sprites.ball_green)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = sprites.ball_red;
+ else if (value === Color.green)
+ this.currentColor = sprites.ball_green;
+ else if (value === Color.blue)
+ this.currentColor = sprites.ball_blue;
+ }
+ });
+
+Object.defineProperty(Ball.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(Ball.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(Ball.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(Ball.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+Ball.prototype.handleInput = function (delta) {
+ if (Mouse.leftPressed && !this.shooting) {
+ this.shooting = true;
+ this.velocity = Mouse.position.subtract(this.position).multiplyWith(1.2);
+ }
+};
+
+Ball.prototype.update = function (delta) {
+ if (this.shooting) {
+ this.velocity.x *= 0.99;
+ this.velocity.y += 6;
+ this.position.addTo(this.velocity.multiply(delta));
+ }
+ else {
+ this.color = Game.gameWorld.cannon.color;
+ this.position = Game.gameWorld.cannon.ballPosition.subtractFrom(this.center);
+ }
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.reset();
+};
+
+Ball.prototype.reset = function () {
+ this.position = new Vector2(65, 390);
+ this.shooting = false;
+};
+
+Ball.prototype.draw = function () {
+ if (!this.shooting)
+ return;
+ Canvas2D.drawImage(this.currentColor, this.position, this.rotation, 1, this.origin);
+};
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/Cannon.js b/10_LimitedLives/Painter8/Cannon.js
new file mode 100644
index 0000000..849a044
--- /dev/null
+++ b/10_LimitedLives/Painter8/Cannon.js
@@ -0,0 +1,96 @@
+"use strict";
+
+function Cannon() {
+ this.position = new Vector2(72, 405);
+ this.origin = new Vector2(34, 34);
+ this.currentColor = sprites.cannon_red;
+ this.velocity = Vector2.zero;
+ this.rotation = 0;
+ this.visible = true;
+}
+
+Object.defineProperty(Cannon.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === sprites.cannon_red)
+ return Color.red;
+ else if (this.currentColor === sprites.cannon_green)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = sprites.cannon_red;
+ else if (value === Color.green)
+ this.currentColor = sprites.cannon_green;
+ else if (value === Color.blue)
+ this.currentColor = sprites.cannon_blue;
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "ballPosition",
+ {
+ get: function () {
+ var opposite = Math.sin(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ var adjacent = Math.cos(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ return new Vector2(this.position.x + adjacent, this.position.y + opposite);
+ }
+ });
+
+
+Cannon.prototype.reset = function () {
+ this.position = new Vector2(72, 405);
+};
+
+Cannon.prototype.handleInput = function (delta) {
+ if (Keyboard.keyDown === Keys.R)
+ this.color = Color.red;
+ else if (Keyboard.keyDown === Keys.G)
+ this.color = Color.green;
+ else if (Keyboard.keyDown === Keys.B)
+ this.color = Color.blue;
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ this.rotation = Math.atan2(opposite, adjacent);
+};
+
+Cannon.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+Cannon.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ var colorPosition = this.position.subtract(this.size.divideBy(2));
+ Canvas2D.drawImage(sprites.cannon_barrel, this.position, this.rotation, 1, this.origin);
+ Canvas2D.drawImage(this.currentColor, colorPosition);
+};
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/Canvas2D.js b/10_LimitedLives/Painter8/Canvas2D.js
new file mode 100644
index 0000000..90a5b9b
--- /dev/null
+++ b/10_LimitedLives/Painter8/Canvas2D.js
@@ -0,0 +1,56 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ console.log("Creating Canvas2D object");
+ this.canvas = null;
+ this.canvasContext = null;
+}
+
+Canvas2D_Singleton.prototype.initialize = function (canvasName) {
+ this.canvas = document.getElementById(canvasName);
+
+ if (this.canvas.getContext)
+ this.canvasContext = this.canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ }
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin) {
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+
+ this.canvasContext.save();
+ this.canvasContext.translate(position.x, position.y);
+ this.canvasContext.rotate(rotation);
+ this.canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x * scale, -origin.y * scale,
+ sprite.width, sprite.height * scale);
+ this.canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, color, textAlign, fontname, fontsize) {
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this.canvasContext.save();
+ this.canvasContext.translate(position.x, position.y);
+ this.canvasContext.textBaseline = 'top';
+ this.canvasContext.font = fontsize + " " + fontname;
+ this.canvasContext.fillStyle = color.toString();
+ this.canvasContext.textAlign = textAlign;
+ this.canvasContext.fillText(text, 0, 0);
+ this.canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/Game.js b/10_LimitedLives/Painter8/Game.js
new file mode 100644
index 0000000..f9392b7
--- /dev/null
+++ b/10_LimitedLives/Painter8/Game.js
@@ -0,0 +1,66 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this.size = null;
+ this.spritesStillLoading = 0;
+ this.gameWorld = null;
+}
+
+Game_Singleton.prototype.start = function (canvasName, x, y) {
+ this.size = new Vector2(x, y);
+
+ Canvas2D.initialize(canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this.spritesStillLoading += 1;
+ image.onload = function () {
+ Game.spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ if (!this.spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+ Mouse.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
+
diff --git a/10_LimitedLives/Painter8/PaintCan.js b/10_LimitedLives/Painter8/PaintCan.js
new file mode 100644
index 0000000..8f5436f
--- /dev/null
+++ b/10_LimitedLives/Painter8/PaintCan.js
@@ -0,0 +1,112 @@
+"use strict";
+
+function PaintCan(xPosition, targetColor) {
+ this.currentColor = sprites.can_red;
+ this.velocity = Vector2.zero;
+ this.position = new Vector2(xPosition, -200);
+ this.origin = Vector2.zero;
+ this.targetColor = targetColor;
+ this.reset();
+}
+
+Object.defineProperty(PaintCan.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === sprites.can_red)
+ return Color.red;
+ else if (this.currentColor === sprites.can_green)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = sprites.can_red;
+ else if (value === Color.green)
+ this.currentColor = sprites.can_green;
+ else if (value === Color.blue)
+ this.currentColor = sprites.can_blue;
+ }
+ });
+
+Object.defineProperty(PaintCan.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(PaintCan.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(PaintCan.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(PaintCan.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+PaintCan.prototype.reset = function () {
+ this.moveToTop();
+ this.minVelocity = 30;
+};
+
+PaintCan.prototype.moveToTop = function () {
+ this.position.y = -200;
+ this.velocity = Vector2.zero;
+};
+
+PaintCan.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+
+ if (this.velocity.y === 0 && Math.random() < 0.01) {
+ this.velocity = this.calculateRandomVelocity();
+ this.color = this.calculateRandomColor();
+ }
+
+ // calculate the distance between this can and the ball
+ var ball_center = Game.gameWorld.ball.center;
+ var ball_position = Game.gameWorld.ball.position;
+ var distance = ball_position.add(ball_center).subtractFrom(this.position).subtractFrom(this.center);
+
+ if (Math.abs(distance.x) < this.center.x && Math.abs(distance.y) < this.center.y) {
+ this.color = Game.gameWorld.ball.color;
+ Game.gameWorld.ball.reset();
+ }
+
+ if (Game.gameWorld.isOutsideWorld(this.position)) {
+ if (this.color !== this.targetColor)
+ Game.gameWorld.lives -= 1;
+ this.moveToTop();
+ }
+ this.minVelocity += 0.01;
+};
+
+PaintCan.prototype.draw = function () {
+ Canvas2D.drawImage(this.currentColor, this.position, this.rotation, 1, this.origin);
+};
+
+PaintCan.prototype.calculateRandomVelocity = function () {
+ return new Vector2(0, Math.random() * 30 + this.minVelocity);
+};
+
+PaintCan.prototype.calculateRandomColor = function () {
+ var randomval = Math.floor(Math.random() * 3);
+ if (randomval == 0)
+ return Color.red;
+ else if (randomval == 1)
+ return Color.green;
+ else
+ return Color.blue;
+};
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/Painter.html b/10_LimitedLives/Painter8/Painter.html
new file mode 100644
index 0000000..478f156
--- /dev/null
+++ b/10_LimitedLives/Painter8/Painter.html
@@ -0,0 +1,34 @@
+
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/Painter.js b/10_LimitedLives/Painter8/Painter.js
new file mode 100644
index 0000000..44d07d2
--- /dev/null
+++ b/10_LimitedLives/Painter8/Painter.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/Painter/sprites/" + sprite);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.cannon_barrel = loadSprite("spr_cannon_barrel.png");
+ sprites.cannon_red = loadSprite("spr_cannon_red.png");
+ sprites.cannon_green = loadSprite("spr_cannon_green.png");
+ sprites.cannon_blue = loadSprite("spr_cannon_blue.png");
+ sprites.ball_red = loadSprite("spr_ball_red.png");
+ sprites.ball_green = loadSprite("spr_ball_green.png");
+ sprites.ball_blue = loadSprite("spr_ball_blue.png");
+ sprites.can_red = loadSprite("spr_can_red.png");
+ sprites.can_green = loadSprite("spr_can_green.png");
+ sprites.can_blue = loadSprite("spr_can_blue.png");
+ sprites.lives = loadSprite("spr_lives.png");
+ sprites.gameover = loadSprite("spr_gameover_click.png");
+};
+
+Game.initialize = function () {
+
+ Game.gameWorld = new PainterGameWorld();
+};
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/PainterGameWorld.js b/10_LimitedLives/Painter8/PainterGameWorld.js
new file mode 100644
index 0000000..edc98cb
--- /dev/null
+++ b/10_LimitedLives/Painter8/PainterGameWorld.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function PainterGameWorld() {
+ this.cannon = new Cannon();
+ this.ball = new Ball();
+
+ this.can1 = new PaintCan(450, Color.red);
+ this.can2 = new PaintCan(575, Color.green);
+ this.can3 = new PaintCan(700, Color.blue);
+
+ this.lives = 5;
+}
+
+PainterGameWorld.prototype.handleInput = function (delta) {
+ if (this.lives > 0) {
+ this.ball.handleInput(delta);
+ this.cannon.handleInput(delta);
+ }
+ else {
+ if (Mouse.leftPressed)
+ this.reset();
+ }
+};
+
+PainterGameWorld.prototype.update = function (delta) {
+ if (this.lives <= 0)
+ return;
+ this.ball.update(delta);
+ this.cannon.update(delta);
+ this.can1.update(delta);
+ this.can2.update(delta);
+ this.can3.update(delta);
+};
+
+PainterGameWorld.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.background);
+
+ this.ball.draw();
+ this.cannon.draw();
+ this.can1.draw();
+ this.can2.draw();
+ this.can3.draw();
+ for (var i = 0; i < this.lives; i++) {
+ Canvas2D.drawImage(sprites.lives, new Vector2(i * sprites.lives.width + 15, 60));
+ }
+ if (this.lives <= 0) {
+ console.log("Game over!");
+ Canvas2D.drawImage(sprites.gameover,
+ new Vector2(Game.size.x - sprites.gameover.width,
+ Game.size.y - sprites.gameover.height).divideBy(2));
+ }
+};
+
+PainterGameWorld.prototype.reset = function () {
+ this.lives = 5;
+ this.cannon.reset();
+ this.ball.reset();
+ this.can1.reset();
+ this.can2.reset();
+ this.can3.reset();
+};
+
+PainterGameWorld.prototype.isOutsideWorld = function (position) {
+ return position.x < 0 || position.x > Game.size.x || position.y > Game.size.y;
+};
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/game-layout.css b/10_LimitedLives/Painter8/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/10_LimitedLives/Painter8/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/geom/Vector2.js b/10_LimitedLives/Painter8/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/10_LimitedLives/Painter8/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/input/Keyboard.js b/10_LimitedLives/Painter8/input/Keyboard.js
new file mode 100644
index 0000000..e28efe9
--- /dev/null
+++ b/10_LimitedLives/Painter8/input/Keyboard.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+function Keyboard_Singleton() {
+ this.keyDown = -1;
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+var Keyboard = new Keyboard_Singleton();
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/input/Mouse.js b/10_LimitedLives/Painter8/input/Mouse.js
new file mode 100644
index 0000000..779d85f
--- /dev/null
+++ b/10_LimitedLives/Painter8/input/Mouse.js
@@ -0,0 +1,33 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse.position = new Vector2(evt.pageX, evt.pageY);
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+function Mouse_Singleton() {
+ this.position = new Vector2();
+ this.leftDown = false;
+ this.leftPressed = false;
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Mouse_Singleton.prototype.reset = function () {
+ this.leftPressed = false;
+};
+
+var Mouse = new Mouse_Singleton();
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/system/Color.js b/10_LimitedLives/Painter8/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/10_LimitedLives/Painter8/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/10_LimitedLives/Painter8/system/Keys.js b/10_LimitedLives/Painter8/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/10_LimitedLives/Painter8/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/LAB.min.js b/11_OrganizingGameObjects/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/11_OrganizingGameObjects/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/Ball.js b/11_OrganizingGameObjects/Painter9/Ball.js
new file mode 100644
index 0000000..4659e1d
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/Ball.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Ball() {
+ ThreeColorGameObject.call(this, sprites.ball_red, sprites.ball_green, sprites.ball_blue);
+ this.shooting = false;
+ this.reset();
+}
+
+Ball.prototype = Object.create(ThreeColorGameObject.prototype);
+
+Ball.prototype.handleInput = function (delta) {
+ if (Mouse.leftPressed && !this.shooting) {
+ this.shooting = true;
+ this.velocity = Mouse.position.subtract(this.position).multiplyWith(1.2);
+ }
+};
+
+Ball.prototype.update = function (delta) {
+ ThreeColorGameObject.prototype.update.call(this, delta);
+ if (this.shooting) {
+ this.velocity.x *= 0.99;
+ this.velocity.y += 6;
+ }
+ else {
+ this.color = Game.gameWorld.cannon.color;
+ this.position = Game.gameWorld.cannon.ballPosition.subtractFrom(this.center);
+ }
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.reset();
+};
+
+Ball.prototype.reset = function () {
+ this.position = new Vector2(65, 390);
+ this.shooting = false;
+};
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/Cannon.js b/11_OrganizingGameObjects/Painter9/Cannon.js
new file mode 100644
index 0000000..621bb57
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/Cannon.js
@@ -0,0 +1,42 @@
+"use strict";
+
+function Cannon() {
+ ThreeColorGameObject.call(this, sprites.cannon_red, sprites.cannon_green, sprites.cannon_blue);
+ this.position = new Vector2(72, 405);
+ this.origin = new Vector2(34, 34);
+}
+
+Cannon.prototype = Object.create(ThreeColorGameObject.prototype);
+
+Object.defineProperty(Cannon.prototype, "ballPosition",
+ {
+ get: function () {
+ var opposite = Math.sin(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ var adjacent = Math.cos(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ return new Vector2(this.position.x + adjacent, this.position.y + opposite);
+ }
+ });
+
+Cannon.prototype.reset = function () {
+ this.position = new Vector2(72, 405);
+};
+
+Cannon.prototype.handleInput = function (delta) {
+ if (Keyboard.keyDown === Keys.R)
+ this.color = Color.red;
+ else if (Keyboard.keyDown === Keys.G)
+ this.color = Color.green;
+ else if (Keyboard.keyDown === Keys.B)
+ this.color = Color.blue;
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ this.rotation = Math.atan2(opposite, adjacent);
+};
+
+Cannon.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ var colorPosition = this.position.subtract(this.size.divideBy(2));
+ Canvas2D.drawImage(sprites.cannon_barrel, this.position, this.rotation, 1, this.origin);
+ Canvas2D.drawImage(this.currentColor, colorPosition);
+};
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/Canvas2D.js b/11_OrganizingGameObjects/Painter9/Canvas2D.js
new file mode 100644
index 0000000..dec06b3
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/Canvas2D.js
@@ -0,0 +1,39 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ console.log("Creating Canvas2D object");
+ this.canvas = null;
+ this.canvasContext = null;
+}
+
+Canvas2D_Singleton.prototype.initialize = function (canvasName) {
+ this.canvas = document.getElementById(canvasName);
+
+ if (this.canvas.getContext)
+ this.canvasContext = this.canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ }
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin) {
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+
+ this.canvasContext.save();
+ this.canvasContext.translate(position.x, position.y);
+ this.canvasContext.rotate(rotation);
+ this.canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x * scale, -origin.y * scale,
+ sprite.width, sprite.height * scale);
+ this.canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/Game.js b/11_OrganizingGameObjects/Painter9/Game.js
new file mode 100644
index 0000000..38ebc43
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/Game.js
@@ -0,0 +1,75 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this.gameWorld = null;
+}
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Game_Singleton.prototype.start = function (canvasName, x, y) {
+ this._size = new Vector2(x, y);
+
+ Canvas2D.initialize(canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ if (!this._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ Mouse.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
+
diff --git a/11_OrganizingGameObjects/Painter9/PaintCan.js b/11_OrganizingGameObjects/Painter9/PaintCan.js
new file mode 100644
index 0000000..efa82a4
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/PaintCan.js
@@ -0,0 +1,60 @@
+"use strict";
+
+function PaintCan(positionOffset, targetColor) {
+ ThreeColorGameObject.call(this, sprites.can_red, sprites.can_green, sprites.can_blue);
+ this.positionOffset = positionOffset;
+ this.targetColor = targetColor;
+ this.reset();
+}
+
+PaintCan.prototype = Object.create(ThreeColorGameObject.prototype);
+
+PaintCan.prototype.reset = function () {
+ this.moveToTop();
+ this.minVelocity = 30;
+};
+
+PaintCan.prototype.moveToTop = function () {
+ this.position = new Vector2(this.positionOffset, -200);
+ this.velocity = Vector2.zero;
+};
+
+PaintCan.prototype.update = function (delta) {
+ ThreeColorGameObject.prototype.update.call(this, delta);
+
+ if (this.velocity.y === 0 && Math.random() < 0.01) {
+ this.velocity = this.calculateRandomVelocity();
+ this.color = this.calculateRandomColor();
+ }
+
+ // calculate the distance between this can and the ball
+ var ball_center = Game.gameWorld.ball.center;
+ var ball_position = Game.gameWorld.ball.position;
+ var distance = ball_position.add(ball_center).subtractFrom(this.position).subtractFrom(this.center);
+
+ if (Math.abs(distance.x) < this.center.x && Math.abs(distance.y) < this.center.y) {
+ this.color = Game.gameWorld.ball.color;
+ Game.gameWorld.ball.reset();
+ }
+
+ if (Game.gameWorld.isOutsideWorld(this.position)) {
+ if (this.color !== this.targetColor)
+ Game.gameWorld.lives -= 1;
+ this.moveToTop();
+ }
+ this.minVelocity += 0.01;
+};
+
+PaintCan.prototype.calculateRandomVelocity = function () {
+ return new Vector2(0, Math.random() * 30 + this.minVelocity);
+};
+
+PaintCan.prototype.calculateRandomColor = function () {
+ var randomval = Math.floor(Math.random() * 3);
+ if (randomval == 0)
+ return Color.red;
+ else if (randomval == 1)
+ return Color.green;
+ else
+ return Color.blue;
+};
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/Painter.html b/11_OrganizingGameObjects/Painter9/Painter.html
new file mode 100644
index 0000000..46fa2d8
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/Painter.html
@@ -0,0 +1,35 @@
+
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/Painter.js b/11_OrganizingGameObjects/Painter9/Painter.js
new file mode 100644
index 0000000..6638e93
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/Painter.js
@@ -0,0 +1,54 @@
+"use strict";
+
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/Painter/sprites/" + sprite);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.cannon_barrel = loadSprite("spr_cannon_barrel.png");
+ sprites.cannon_red = loadSprite("spr_cannon_red.png");
+ sprites.cannon_green = loadSprite("spr_cannon_green.png");
+ sprites.cannon_blue = loadSprite("spr_cannon_blue.png");
+ sprites.ball_red = loadSprite("spr_ball_red.png");
+ sprites.ball_green = loadSprite("spr_ball_green.png");
+ sprites.ball_blue = loadSprite("spr_ball_blue.png");
+ sprites.can_red = loadSprite("spr_can_red.png");
+ sprites.can_green = loadSprite("spr_can_green.png");
+ sprites.can_blue = loadSprite("spr_can_blue.png");
+ sprites.lives = loadSprite("spr_lives.png");
+ sprites.gameover = loadSprite("spr_gameover_click.png");
+};
+
+Game.initialize = function () {
+
+ Game.gameWorld = new PainterGameWorld();
+};
+
+function Vehicle() {
+ this.numberOfWheels = 0;
+ this.brand = "";
+}
+
+Vehicle.prototype.what = function() {
+ return "nrOfWheels = " + this.numberOfWheels + ", brand = " + this.brand;
+};
+
+function Car(brand) {
+ Vehicle.call(this);
+ this.numberOfWeels = 4;
+ this.brand = brand;
+ this.convertible = false;
+}
+Car.prototype = Object.create(Vehicle.prototype);
+
+function Motorcycle(brand) {
+ Vehicle.call(this);
+ this.numberOfWheels = 2;
+ this.brand = brand;
+ this.cylinders = 4;
+}
+Motorcycle.prototype = Object.create(Vehicle.prototype);
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/PainterGameWorld.js b/11_OrganizingGameObjects/Painter9/PainterGameWorld.js
new file mode 100644
index 0000000..bc6a2e9
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/PainterGameWorld.js
@@ -0,0 +1,64 @@
+"use strict";
+
+function PainterGameWorld() {
+ this.cannon = new Cannon();
+ this.ball = new Ball();
+
+ this.can1 = new PaintCan(450, Color.red);
+ this.can2 = new PaintCan(575, Color.green);
+ this.can3 = new PaintCan(700, Color.blue);
+
+ this.lives = 5;
+}
+
+PainterGameWorld.prototype.handleInput = function (delta) {
+ if (this.lives > 0) {
+ this.ball.handleInput(delta);
+ this.cannon.handleInput(delta);
+ }
+ else {
+ if (Mouse.left.pressed || Touch.pressed)
+ this.reset();
+ }
+};
+
+PainterGameWorld.prototype.update = function (delta) {
+ if (this.lives > 0) {
+ this.ball.update(delta);
+ this.cannon.update(delta);
+ this.can1.update(delta);
+ this.can2.update(delta);
+ this.can3.update(delta);
+ }
+};
+
+PainterGameWorld.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.background);
+
+ this.ball.draw();
+ this.cannon.draw();
+ this.can1.draw();
+ this.can2.draw();
+ this.can3.draw();
+ for (var i = 0; i < this.lives; i++) {
+ Canvas2D.drawImage(sprites.lives, new Vector2(i * sprites.lives.width + 15, 60));
+ }
+ if (this.lives <= 0)
+ Canvas2D.drawImage(sprites.gameover,
+ new Vector2(Game.size.x - sprites.gameover.width,
+ Game.size.y - sprites.gameover.height).divideBy(2));
+};
+
+PainterGameWorld.prototype.reset = function () {
+ this.score = 0;
+ this.lives = 5;
+ this.cannon.reset();
+ this.ball.reset();
+ this.can1.reset();
+ this.can2.reset();
+ this.can3.reset();
+};
+
+PainterGameWorld.prototype.isOutsideWorld = function (position) {
+ return position.x < 0 || position.x > Game.size.x || position.y > Game.size.y;
+};
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/ThreeColorGameObject.js b/11_OrganizingGameObjects/Painter9/ThreeColorGameObject.js
new file mode 100644
index 0000000..9e0fa92
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/ThreeColorGameObject.js
@@ -0,0 +1,71 @@
+"use strict";
+
+function ThreeColorGameObject(sprColorRed, sprColorGreen, sprColorBlue) {
+ this.colorRed = sprColorRed;
+ this.colorGreen = sprColorGreen;
+ this.colorBlue = sprColorBlue;
+ this.currentColor = this.colorRed;
+ this.velocity = Vector2.zero;
+ this.position = Vector2.zero;
+ this.origin = Vector2.zero;
+ this.rotation = 0;
+ this.visible = true;
+}
+
+Object.defineProperty(ThreeColorGameObject.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === this.colorRed)
+ return Color.red;
+ else if (this.currentColor === this.colorGreen)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = this.colorRed;
+ else if (value === Color.green)
+ this.currentColor = this.colorGreen;
+ else if (value === Color.blue)
+ this.currentColor = this.colorBlue;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+ThreeColorGameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+ThreeColorGameObject.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawImage(this.currentColor, this.position, this.rotation, 1, this.origin);
+};
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/game-layout.css b/11_OrganizingGameObjects/Painter9/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/geom/Vector2.js b/11_OrganizingGameObjects/Painter9/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/input/Keyboard.js b/11_OrganizingGameObjects/Painter9/input/Keyboard.js
new file mode 100644
index 0000000..62a1e63
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/input/Keyboard.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+function Keyboard_Singleton() {
+ this.keyDown = -1;
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this.keyDown === key;
+};
+
+var Keyboard = new Keyboard_Singleton();
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/input/Mouse.js b/11_OrganizingGameObjects/Painter9/input/Mouse.js
new file mode 100644
index 0000000..779d85f
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/input/Mouse.js
@@ -0,0 +1,33 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse.position = new Vector2(evt.pageX, evt.pageY);
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+function Mouse_Singleton() {
+ this.position = new Vector2();
+ this.leftDown = false;
+ this.leftPressed = false;
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Mouse_Singleton.prototype.reset = function () {
+ this.leftPressed = false;
+};
+
+var Mouse = new Mouse_Singleton();
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/system/Color.js b/11_OrganizingGameObjects/Painter9/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/11_OrganizingGameObjects/Painter9/system/Keys.js b/11_OrganizingGameObjects/Painter9/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/11_OrganizingGameObjects/Painter9/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/12_FinishingGame/LAB.min.js b/12_FinishingGame/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/12_FinishingGame/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/Ball.js b/12_FinishingGame/PainterFinal/Ball.js
new file mode 100644
index 0000000..66fa100
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/Ball.js
@@ -0,0 +1,36 @@
+"use strict";
+
+function Ball() {
+ ThreeColorGameObject.call(this, sprites.ball_red, sprites.ball_green, sprites.ball_blue);
+ this.shooting = false;
+ this.reset();
+}
+
+Ball.prototype = Object.create(ThreeColorGameObject.prototype);
+
+Ball.prototype.handleInput = function (delta) {
+ if (Mouse.leftPressed && !this.shooting) {
+ this.shooting = true;
+ this.velocity = Mouse.position.subtract(this.position).multiplyWith(1.2);
+ sounds.shoot_paint.play();
+ }
+};
+
+Ball.prototype.update = function (delta) {
+ if (this.shooting) {
+ this.velocity.x *= 0.99;
+ this.velocity.y += 6;
+ ThreeColorGameObject.prototype.update.call(this, delta);
+ }
+ else {
+ this.color = Game.gameWorld.cannon.color;
+ this.position = Game.gameWorld.cannon.ballPosition.subtractFrom(this.center);
+ }
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.reset();
+};
+
+Ball.prototype.reset = function () {
+ this.position = new Vector2(65, 390);
+ this.shooting = false;
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/Cannon.js b/12_FinishingGame/PainterFinal/Cannon.js
new file mode 100644
index 0000000..0f7d168
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/Cannon.js
@@ -0,0 +1,43 @@
+"use strict";
+
+function Cannon() {
+ ThreeColorGameObject.call(this, sprites.cannon_red, sprites.cannon_green, sprites.cannon_blue);
+ this.position = new Vector2(72, 405);
+ this.origin = new Vector2(34, 34);
+}
+
+Cannon.prototype = Object.create(ThreeColorGameObject.prototype);
+
+Object.defineProperty(Cannon.prototype, "ballPosition",
+ {
+ get: function () {
+ var opposite = Math.sin(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ var adjacent = Math.cos(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ return new Vector2(this.position.x + adjacent, this.position.y + opposite);
+ }
+ });
+
+
+Cannon.prototype.reset = function () {
+ this.position = new Vector2(72, 405);
+};
+
+Cannon.prototype.handleInput = function (delta) {
+ if (Keyboard.keyDown === Keys.R)
+ this.color = Color.red;
+ else if (Keyboard.keyDown === Keys.G)
+ this.color = Color.green;
+ else if (Keyboard.keyDown === Keys.B)
+ this.color = Color.blue;
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ this.rotation = Math.atan2(opposite, adjacent);
+};
+
+Cannon.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ var colorPosition = this.position.subtract(this.size.divideBy(2));
+ Canvas2D.drawImage(sprites.cannon_barrel, this.position, this.rotation, 1, this.origin);
+ Canvas2D.drawImage(this.currentColor, colorPosition);
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/Canvas2D.js b/12_FinishingGame/PainterFinal/Canvas2D.js
new file mode 100644
index 0000000..b895453
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/Canvas2D.js
@@ -0,0 +1,56 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ console.log("Creating Canvas2D object");
+ this._canvas = null;
+ this._canvasContext = null;
+}
+
+Canvas2D_Singleton.prototype.initialize = function (canvasName) {
+ this._canvas = document.getElementById(canvasName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ }
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin) {
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+
+ this._canvasContext.save();
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x * scale, -origin.y * scale,
+ sprite.width, sprite.height * scale);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, color, textAlign, fontname, fontsize) {
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/Game.js b/12_FinishingGame/PainterFinal/Game.js
new file mode 100644
index 0000000..ee748a3
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/Game.js
@@ -0,0 +1,76 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ console.log("Creating game");
+ this._totalTime = 0;
+ this.size = null;
+ this._spritesStillLoading = 0;
+ this.gameWorld = null;
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Game_Singleton.prototype.start = function (canvasName, x, y) {
+ this.size = new Vector2(x, y);
+
+ Canvas2D.initialize(canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+ console.log("'");
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ if (!this._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ Mouse.reset();
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
+
diff --git a/12_FinishingGame/PainterFinal/PaintCan.js b/12_FinishingGame/PainterFinal/PaintCan.js
new file mode 100644
index 0000000..b953a47
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/PaintCan.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function PaintCan(positionOffset, targetColor) {
+ ThreeColorGameObject.call(this, sprites.can_red, sprites.can_green, sprites.can_blue);
+ this.positionOffset = positionOffset;
+ this.targetColor = targetColor;
+ this.reset();
+}
+
+PaintCan.prototype = Object.create(ThreeColorGameObject.prototype);
+
+PaintCan.prototype.reset = function () {
+ this.moveToTop();
+ this.minVelocity = 30;
+};
+
+PaintCan.prototype.moveToTop = function () {
+ this.position = new Vector2(this.positionOffset, -200);
+ this.velocity = Vector2.zero;
+};
+
+PaintCan.prototype.update = function (delta) {
+ ThreeColorGameObject.prototype.update.call(this, delta);
+
+ if (this.velocity.y === 0 && Math.random() < 0.01) {
+ this.velocity = this.calculateRandomVelocity();
+ this.color = this.calculateRandomColor();
+ }
+
+ // calculate the distance between this can and the ball
+ var ball_center = Game.gameWorld.ball.center;
+ var ball_position = Game.gameWorld.ball.position;
+ var distance = ball_position.add(ball_center).subtractFrom(this.position).subtractFrom(this.center);
+
+ if (Math.abs(distance.x) < this.center.x && Math.abs(distance.y) < this.center.y) {
+ this.color = Game.gameWorld.ball.color;
+ Game.gameWorld.ball.reset();
+ }
+
+ if (Game.gameWorld.isOutsideWorld(this.position)) {
+ if (this.color === this.targetColor) {
+ Game.gameWorld.score += 10;
+ sounds.collect_points.play();
+ }
+ else
+ Game.gameWorld.lives -= 1;
+ this.moveToTop();
+ }
+ this.minVelocity += 0.01;
+ this.rotation = Math.sin(this.position.y / 50) * 0.05;
+};
+
+PaintCan.prototype.calculateRandomVelocity = function () {
+ return new Vector2(0, Math.random() * 30 + this.minVelocity);
+};
+
+PaintCan.prototype.calculateRandomColor = function () {
+ var randomval = Math.floor(Math.random() * 3);
+ if (randomval == 0)
+ return Color.red;
+ else if (randomval == 1)
+ return Color.green;
+ else
+ return Color.blue;
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/Painter.html b/12_FinishingGame/PainterFinal/Painter.html
new file mode 100644
index 0000000..ba04531
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/Painter.html
@@ -0,0 +1,36 @@
+
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/Painter.js b/12_FinishingGame/PainterFinal/Painter.js
new file mode 100644
index 0000000..77de59a
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/Painter.js
@@ -0,0 +1,42 @@
+"use strict";
+
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/Painter/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/Painter/sounds/" + sound, looping);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.scorebar = loadSprite("spr_scorebar.jpg");
+ sprites.cannon_barrel = loadSprite("spr_cannon_barrel.png");
+ sprites.cannon_red = loadSprite("spr_cannon_red.png");
+ sprites.cannon_green = loadSprite("spr_cannon_green.png");
+ sprites.cannon_blue = loadSprite("spr_cannon_blue.png");
+ sprites.ball_red = loadSprite("spr_ball_red.png");
+ sprites.ball_green = loadSprite("spr_ball_green.png");
+ sprites.ball_blue = loadSprite("spr_ball_blue.png");
+ sprites.can_red = loadSprite("spr_can_red.png");
+ sprites.can_green = loadSprite("spr_can_green.png");
+ sprites.can_blue = loadSprite("spr_can_blue.png");
+ sprites.lives = loadSprite("spr_lives.png");
+ sprites.gameover = loadSprite("spr_gameover_click.png");
+
+ sounds.music = loadSound("snd_music");
+ sounds.collect_points = loadSound("snd_collect_points");
+ sounds.shoot_paint = loadSound("snd_shoot_paint");
+};
+
+Game.initialize = function () {
+ // sound
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // create the game world
+ Game.gameWorld = new PainterGameWorld();
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/PainterGameWorld.js b/12_FinishingGame/PainterFinal/PainterGameWorld.js
new file mode 100644
index 0000000..9047bad
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/PainterGameWorld.js
@@ -0,0 +1,67 @@
+"use strict";
+
+function PainterGameWorld() {
+ this.cannon = new Cannon();
+ this.ball = new Ball();
+
+ this.can1 = new PaintCan(450, Color.red);
+ this.can2 = new PaintCan(575, Color.green);
+ this.can3 = new PaintCan(700, Color.blue);
+
+ this.score = 0;
+ this.lives = 5;
+}
+
+PainterGameWorld.prototype.handleInput = function (delta) {
+ if (this.lives > 0) {
+ this.ball.handleInput(delta);
+ this.cannon.handleInput(delta);
+ }
+ else {
+ if (Mouse.leftPressed)
+ this.reset();
+ }
+};
+
+PainterGameWorld.prototype.update = function (delta) {
+ if (this.lives > 0) {
+ this.ball.update(delta);
+ this.cannon.update(delta);
+ this.can1.update(delta);
+ this.can2.update(delta);
+ this.can3.update(delta);
+ }
+};
+
+PainterGameWorld.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.background);
+ Canvas2D.drawImage(sprites.scorebar, new Vector2(10, 10));
+ Canvas2D.drawText("Score: " + this.score, new Vector2(20, 22), Color.white);
+
+ this.ball.draw();
+ this.cannon.draw();
+ this.can1.draw();
+ this.can2.draw();
+ this.can3.draw();
+ for (var i = 0; i < this.lives; i++) {
+ Canvas2D.drawImage(sprites.lives, new Vector2(i * sprites.lives.width + 15, 60));
+ }
+ if (this.lives <= 0)
+ Canvas2D.drawImage(sprites.gameover,
+ new Vector2(Game.size.x - sprites.gameover.width,
+ Game.size.y - sprites.gameover.height).divideBy(2));
+};
+
+PainterGameWorld.prototype.reset = function () {
+ this.score = 0;
+ this.lives = 5;
+ this.cannon.reset();
+ this.ball.reset();
+ this.can1.reset();
+ this.can2.reset();
+ this.can3.reset();
+};
+
+PainterGameWorld.prototype.isOutsideWorld = function (position) {
+ return position.x < 0 || position.x > Game.size.x || position.y > Game.size.y;
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/Sound.js b/12_FinishingGame/PainterFinal/Sound.js
new file mode 100644
index 0000000..5d9671b
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function (value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function () {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/ThreeColorGameObject.js b/12_FinishingGame/PainterFinal/ThreeColorGameObject.js
new file mode 100644
index 0000000..1a397a2
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/ThreeColorGameObject.js
@@ -0,0 +1,71 @@
+"use strict";
+
+function ThreeColorGameObject(spr_color_red, spr_color_green, spr_color_blue) {
+ this.colorRed = spr_color_red;
+ this.colorGreen = spr_color_green;
+ this.colorBlue = spr_color_blue;
+ this.currentColor = this.colorRed;
+ this.velocity = Vector2.zero;
+ this.position = Vector2.zero;
+ this.origin = Vector2.zero;
+ this.rotation = 0;
+ this.visible = true;
+}
+
+Object.defineProperty(ThreeColorGameObject.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === this.colorRed)
+ return Color.red;
+ else if (this.currentColor === this.colorGreen)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = this.colorRed;
+ else if (value === Color.green)
+ this.currentColor = this.colorGreen;
+ else if (value === Color.blue)
+ this.currentColor = this.colorBlue;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+ThreeColorGameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+ThreeColorGameObject.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawImage(this.currentColor, this.position, this.rotation, 1, this.origin);
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/game-layout.css b/12_FinishingGame/PainterFinal/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/geom/Vector2.js b/12_FinishingGame/PainterFinal/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/input/Keyboard.js b/12_FinishingGame/PainterFinal/input/Keyboard.js
new file mode 100644
index 0000000..62a1e63
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/input/Keyboard.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ Keyboard.keyDown = evt.keyCode;
+}
+
+function handleKeyUp(evt) {
+ Keyboard.keyDown = -1;
+}
+
+function Keyboard_Singleton() {
+ this.keyDown = -1;
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this.keyDown === key;
+};
+
+var Keyboard = new Keyboard_Singleton();
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/input/Mouse.js b/12_FinishingGame/PainterFinal/input/Mouse.js
new file mode 100644
index 0000000..779d85f
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/input/Mouse.js
@@ -0,0 +1,33 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse.position = new Vector2(evt.pageX, evt.pageY);
+}
+
+function handleMouseDown(evt) {
+ if (evt.which === 1) {
+ if (!Mouse.leftDown)
+ Mouse.leftPressed = true;
+ Mouse.leftDown = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ if (evt.which === 1)
+ Mouse.leftDown = false;
+}
+
+function Mouse_Singleton() {
+ this.position = new Vector2();
+ this.leftDown = false;
+ this.leftPressed = false;
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Mouse_Singleton.prototype.reset = function () {
+ this.leftPressed = false;
+};
+
+var Mouse = new Mouse_Singleton();
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/system/Color.js b/12_FinishingGame/PainterFinal/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/12_FinishingGame/PainterFinal/system/Keys.js b/12_FinishingGame/PainterFinal/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/12_FinishingGame/PainterFinal/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/Canvas2D.js b/13_AdaptingDevices/JewelJam1/Canvas2D.js
new file mode 100644
index 0000000..cfc68bd
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/Canvas2D.js
@@ -0,0 +1,117 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+ window.onresize = Canvas2D_Singleton.prototype.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x * scale, -origin.y * scale,
+ sprite.width * scale, sprite.height * scale);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
+
diff --git a/13_AdaptingDevices/JewelJam1/Game.js b/13_AdaptingDevices/JewelJam1/Game.js
new file mode 100644
index 0000000..bab8420
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/Game.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ this.gameWorld = null;
+
+ // for debugging
+ this.log = "";
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ this._totalSprites += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ // displaying the number of touches
+ Canvas2D.drawText(Game.log);
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/JewelJam.html b/13_AdaptingDevices/JewelJam1/JewelJam.html
new file mode 100644
index 0000000..5ba6d5a
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/JewelJam.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ Jewel Jam
+
+
+
+ Jewel Jam
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/JewelJam.js b/13_AdaptingDevices/JewelJam1/JewelJam.js
new file mode 100644
index 0000000..a77253d
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/JewelJam.js
@@ -0,0 +1,34 @@
+"use strict";
+
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/JewelJam/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/JewelJam/sounds/" + sound, looping);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.jewel = loadSprite("spr_single_jewel1.png");
+};
+
+Game.initialize = function () {
+ Game.gameWorld = new JewelJamGameWorld();
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/13_AdaptingDevices/JewelJam1/JewelJamGameWorld.js b/13_AdaptingDevices/JewelJam1/JewelJamGameWorld.js
new file mode 100644
index 0000000..12368e9
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/JewelJamGameWorld.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function JewelJamGameWorld(layer, id) {
+
+}
+
+JewelJamGameWorld.prototype.handleInput = function (delta) {
+
+};
+
+JewelJamGameWorld.prototype.update = function (delta) {
+
+};
+
+JewelJamGameWorld.prototype.draw = function (delta) {
+ Canvas2D.drawImage(sprites.background);
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/Sound.js b/13_AdaptingDevices/JewelJam1/Sound.js
new file mode 100644
index 0000000..3782425
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function (value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function() {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/game-layout.css b/13_AdaptingDevices/JewelJam1/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/geom/Rectangle.js b/13_AdaptingDevices/JewelJam1/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/geom/Vector2.js b/13_AdaptingDevices/JewelJam1/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/input/ButtonState.js b/13_AdaptingDevices/JewelJam1/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/input/Keyboard.js b/13_AdaptingDevices/JewelJam1/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/13_AdaptingDevices/JewelJam1/input/Mouse.js b/13_AdaptingDevices/JewelJam1/input/Mouse.js
new file mode 100644
index 0000000..22bc378
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/13_AdaptingDevices/JewelJam1/input/Touch.js b/13_AdaptingDevices/JewelJam1/input/Touch.js
new file mode 100644
index 0000000..fea1e09
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/input/Touch.js
@@ -0,0 +1,124 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isPressing",
+ {
+ get: function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ if (this._touchPresses[i]) {
+ return true;
+ }
+ return false;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.isValid = function (index) {
+ return index >= 0 && index < this._touches.length;
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/system/Color.js b/13_AdaptingDevices/JewelJam1/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/JewelJam1/system/Keys.js b/13_AdaptingDevices/JewelJam1/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/13_AdaptingDevices/JewelJam1/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/LAB.min.js b/13_AdaptingDevices/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/13_AdaptingDevices/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/Ball.js b/13_AdaptingDevices/PainterWithTouch/Ball.js
new file mode 100644
index 0000000..1207b11
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/Ball.js
@@ -0,0 +1,58 @@
+"use strict";
+
+function Ball() {
+ ThreeColorGameObject.call(this, sprites.ball_red, sprites.ball_green, sprites.ball_blue);
+ this.shooting = false;
+ this.readyToShoot = false;
+ this.lastTouchPosition = Vector2.zero;
+ this.reset();
+}
+
+Ball.prototype = Object.create(ThreeColorGameObject.prototype);
+
+Ball.prototype.handleInput = function (delta) {
+ if (Touch.isTouchDevice)
+ this.handleInputTouch(delta);
+ else
+ this.handleInputMouse(delta);
+};
+
+Ball.prototype.handleInputMouse = function (delta) {
+ if (Mouse.left.pressed && !this.shooting) {
+ this.shooting = true;
+ this.velocity = Mouse.position.subtract(this.position).multiplyWith(1.2);
+ sounds.shoot_paint.play();
+ }
+};
+
+Ball.prototype.handleInputTouch = function (delta) {
+ if (Touch.isTouching && !Touch.containsTouch(Game.gameWorld.cannon.colorSelectRectangle)) {
+ this.lastTouchPosition = Touch.getPosition(0).copy();
+ this.readyToShoot = true;
+ }
+ if (!Touch.isTouching && this.readyToShoot && !this.shooting) {
+ this.shooting = true;
+ this.readyToShoot = false;
+ this.velocity = this.lastTouchPosition.subtract(this.position).multiplyWith(1.2);
+ sounds.shoot_paint.play();
+ }
+};
+
+Ball.prototype.update = function (delta) {
+ if (this.shooting) {
+ this.velocity.x *= 0.99;
+ this.velocity.y += 6;
+ ThreeColorGameObject.prototype.update.call(this, delta);
+ }
+ else {
+ this.color = Game.gameWorld.cannon.color;
+ this.position = Game.gameWorld.cannon.ballPosition.subtractFrom(this.center);
+ }
+ if (Game.gameWorld.isOutsideWorld(this.position))
+ this.reset();
+};
+
+Ball.prototype.reset = function () {
+ this.position = new Vector2(65, 390);
+ this.shooting = false;
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/Cannon.js b/13_AdaptingDevices/PainterWithTouch/Cannon.js
new file mode 100644
index 0000000..1217787
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/Cannon.js
@@ -0,0 +1,75 @@
+"use strict";
+
+function Cannon() {
+ ThreeColorGameObject.call(this, sprites.cannon_red, sprites.cannon_green, sprites.cannon_blue);
+ this.position = new Vector2(72, 405);
+ this.origin = new Vector2(34, 34);
+}
+
+Cannon.prototype = Object.create(ThreeColorGameObject.prototype);
+
+Object.defineProperty(Cannon.prototype, "colorSelectRectangle",
+ {
+ get: function () {
+ return new Rectangle(this.position.x - this.origin.x, this.position.y - this.origin.y,
+ sprites.cannon_barrel.height, sprites.cannon_barrel.height);
+ }
+ });
+
+Object.defineProperty(Cannon.prototype, "ballPosition",
+ {
+ get: function () {
+ var opposite = Math.sin(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ var adjacent = Math.cos(this.rotation) * sprites.cannon_barrel.width * 0.6;
+ return new Vector2(this.position.x + adjacent, this.position.y + opposite);
+ }
+ });
+
+
+Cannon.prototype.reset = function () {
+ this.position = new Vector2(72, 405);
+};
+
+Cannon.prototype.handleInput = function (delta) {
+ if (Touch.isTouchDevice)
+ this.handleInputTouch(delta);
+ else
+ this.handleInputMouse(delta);
+};
+
+Cannon.prototype.handleInputMouse = function (delta) {
+ if (Keyboard.pressed(Keys.R))
+ this.currentColor = this.colorRed;
+ else if (Keyboard.pressed(Keys.G))
+ this.currentColor = this.colorGreen;
+ else if (Keyboard.pressed(Keys.B))
+ this.currentColor = this.colorBlue;
+ var opposite = Mouse.position.y - this.position.y;
+ var adjacent = Mouse.position.x - this.position.x;
+ this.rotation = Math.atan2(opposite, adjacent);
+};
+
+Cannon.prototype.handleInputTouch = function (delta) {
+ var rect = this.colorSelectRectangle;
+ if (Touch.containsTouchPress(rect)) {
+ if (this.currentColor === this.colorRed)
+ this.currentColor = this.colorGreen;
+ else if (this.currentColor === this.colorGreen)
+ this.currentColor = this.colorBlue;
+ else
+ this.currentColor = this.colorRed;
+ } else if (Touch.isTouching && !Touch.containsTouch(rect)) {
+ var touchPos = Touch.getPosition(0);
+ var opposite = touchPos.y - this.position.y;
+ var adjacent = touchPos.x - this.position.x;
+ this.rotation = Math.atan2(opposite, adjacent);
+ }
+};
+
+Cannon.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ var colorPosition = this.position.subtract(this.size.divideBy(2));
+ Canvas2D.drawImage(sprites.cannon_barrel, this.position, this.rotation, 1, this.origin);
+ Canvas2D.drawImage(this.currentColor, colorPosition);
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/Canvas2D.js b/13_AdaptingDevices/PainterWithTouch/Canvas2D.js
new file mode 100644
index 0000000..b895453
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/Canvas2D.js
@@ -0,0 +1,56 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ console.log("Creating Canvas2D object");
+ this._canvas = null;
+ this._canvasContext = null;
+}
+
+Canvas2D_Singleton.prototype.initialize = function (canvasName) {
+ this._canvas = document.getElementById(canvasName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ }
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin) {
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+
+ this._canvasContext.save();
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, 0, 0,
+ sprite.width, sprite.height,
+ -origin.x * scale, -origin.y * scale,
+ sprite.width, sprite.height * scale);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, color, textAlign, fontname, fontsize) {
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/Game.js b/13_AdaptingDevices/PainterWithTouch/Game.js
new file mode 100644
index 0000000..2790258
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/Game.js
@@ -0,0 +1,87 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ console.log("Creating game");
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this.gameWorld = null;
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Game_Singleton.prototype.start = function (canvasName, x, y) {
+ this._size = new Vector2(x, y);
+
+ Canvas2D.initialize(canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ if (!this._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
+
diff --git a/13_AdaptingDevices/PainterWithTouch/PaintCan.js b/13_AdaptingDevices/PainterWithTouch/PaintCan.js
new file mode 100644
index 0000000..b953a47
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/PaintCan.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function PaintCan(positionOffset, targetColor) {
+ ThreeColorGameObject.call(this, sprites.can_red, sprites.can_green, sprites.can_blue);
+ this.positionOffset = positionOffset;
+ this.targetColor = targetColor;
+ this.reset();
+}
+
+PaintCan.prototype = Object.create(ThreeColorGameObject.prototype);
+
+PaintCan.prototype.reset = function () {
+ this.moveToTop();
+ this.minVelocity = 30;
+};
+
+PaintCan.prototype.moveToTop = function () {
+ this.position = new Vector2(this.positionOffset, -200);
+ this.velocity = Vector2.zero;
+};
+
+PaintCan.prototype.update = function (delta) {
+ ThreeColorGameObject.prototype.update.call(this, delta);
+
+ if (this.velocity.y === 0 && Math.random() < 0.01) {
+ this.velocity = this.calculateRandomVelocity();
+ this.color = this.calculateRandomColor();
+ }
+
+ // calculate the distance between this can and the ball
+ var ball_center = Game.gameWorld.ball.center;
+ var ball_position = Game.gameWorld.ball.position;
+ var distance = ball_position.add(ball_center).subtractFrom(this.position).subtractFrom(this.center);
+
+ if (Math.abs(distance.x) < this.center.x && Math.abs(distance.y) < this.center.y) {
+ this.color = Game.gameWorld.ball.color;
+ Game.gameWorld.ball.reset();
+ }
+
+ if (Game.gameWorld.isOutsideWorld(this.position)) {
+ if (this.color === this.targetColor) {
+ Game.gameWorld.score += 10;
+ sounds.collect_points.play();
+ }
+ else
+ Game.gameWorld.lives -= 1;
+ this.moveToTop();
+ }
+ this.minVelocity += 0.01;
+ this.rotation = Math.sin(this.position.y / 50) * 0.05;
+};
+
+PaintCan.prototype.calculateRandomVelocity = function () {
+ return new Vector2(0, Math.random() * 30 + this.minVelocity);
+};
+
+PaintCan.prototype.calculateRandomColor = function () {
+ var randomval = Math.floor(Math.random() * 3);
+ if (randomval == 0)
+ return Color.red;
+ else if (randomval == 1)
+ return Color.green;
+ else
+ return Color.blue;
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/Painter.html b/13_AdaptingDevices/PainterWithTouch/Painter.html
new file mode 100644
index 0000000..95bf53e
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/Painter.html
@@ -0,0 +1,39 @@
+
+
+
+
+ Painter
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/Painter.js b/13_AdaptingDevices/PainterWithTouch/Painter.js
new file mode 100644
index 0000000..b386308
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/Painter.js
@@ -0,0 +1,45 @@
+"use strict";
+
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/Painter/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/Painter/sounds/" + sound, looping);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.scorebar = loadSprite("spr_scorebar.jpg");
+ sprites.cannon_barrel = loadSprite("spr_cannon_barrel.png");
+ sprites.cannon_red = loadSprite("spr_cannon_red.png");
+ sprites.cannon_green = loadSprite("spr_cannon_green.png");
+ sprites.cannon_blue = loadSprite("spr_cannon_blue.png");
+ sprites.ball_red = loadSprite("spr_ball_red.png");
+ sprites.ball_green = loadSprite("spr_ball_green.png");
+ sprites.ball_blue = loadSprite("spr_ball_blue.png");
+ sprites.can_red = loadSprite("spr_can_red.png");
+ sprites.can_green = loadSprite("spr_can_green.png");
+ sprites.can_blue = loadSprite("spr_can_blue.png");
+ sprites.lives = loadSprite("spr_lives.png");
+ if (Touch.isTouchDevice)
+ sprites.gameover = loadSprite("spr_gameover_tap.png");
+ else
+ sprites.gameover = loadSprite("spr_gameover_click.png");
+
+ sounds.music = loadSound("snd_music");
+ sounds.collect_points = loadSound("snd_collect_points");
+ sounds.shoot_paint = loadSound("snd_shoot_paint");
+};
+
+Game.initialize = function () {
+ // sound
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // create the game world
+ Game.gameWorld = new PainterGameWorld();
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/PainterGameWorld.js b/13_AdaptingDevices/PainterWithTouch/PainterGameWorld.js
new file mode 100644
index 0000000..cfcc3c3
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/PainterGameWorld.js
@@ -0,0 +1,67 @@
+"use strict";
+
+function PainterGameWorld() {
+ this.cannon = new Cannon();
+ this.ball = new Ball();
+
+ this.can1 = new PaintCan(450, Color.red);
+ this.can2 = new PaintCan(575, Color.green);
+ this.can3 = new PaintCan(700, Color.blue);
+
+ this.score = 0;
+ this.lives = 5;
+}
+
+PainterGameWorld.prototype.handleInput = function (delta) {
+ if (this.lives > 0) {
+ this.ball.handleInput(delta);
+ this.cannon.handleInput(delta);
+ }
+ else {
+ if (Mouse.left.pressed || Touch.isPressing)
+ this.reset();
+ }
+};
+
+PainterGameWorld.prototype.update = function (delta) {
+ if (this.lives > 0) {
+ this.ball.update(delta);
+ this.cannon.update(delta);
+ this.can1.update(delta);
+ this.can2.update(delta);
+ this.can3.update(delta);
+ }
+};
+
+PainterGameWorld.prototype.draw = function () {
+ Canvas2D.drawImage(sprites.background);
+ Canvas2D.drawImage(sprites.scorebar, new Vector2(10, 10));
+ Canvas2D.drawText("Score: " + this.score, new Vector2(20, 22), Color.white);
+
+ this.ball.draw();
+ this.cannon.draw();
+ this.can1.draw();
+ this.can2.draw();
+ this.can3.draw();
+ for (var i = 0; i < this.lives; i++) {
+ Canvas2D.drawImage(sprites.lives, new Vector2(i * sprites.lives.width + 15, 60));
+ }
+ if (this.lives <= 0)
+ Canvas2D.drawImage(sprites.gameover,
+ new Vector2(Game.size.x - sprites.gameover.width,
+ Game.size.y - sprites.gameover.height).divideBy(2));
+};
+
+PainterGameWorld.prototype.reset = function () {
+ this.score = 0;
+ this.lives = 5;
+ this.cannon.reset();
+ this.ball.reset();
+ this.can1.reset();
+ this.can2.reset();
+ this.can3.reset();
+};
+
+PainterGameWorld.prototype.isOutsideWorld = function (position) {
+ return position.x < 0 || position.x > Game.size.x || position.y > Game.size.y;
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/Sound.js b/13_AdaptingDevices/PainterWithTouch/Sound.js
new file mode 100644
index 0000000..468570a
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/Sound.js
@@ -0,0 +1,36 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function (value) {
+ this.snd.volume = value;
+ console.log("Setting volume");
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function () {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/ThreeColorGameObject.js b/13_AdaptingDevices/PainterWithTouch/ThreeColorGameObject.js
new file mode 100644
index 0000000..1a397a2
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/ThreeColorGameObject.js
@@ -0,0 +1,71 @@
+"use strict";
+
+function ThreeColorGameObject(spr_color_red, spr_color_green, spr_color_blue) {
+ this.colorRed = spr_color_red;
+ this.colorGreen = spr_color_green;
+ this.colorBlue = spr_color_blue;
+ this.currentColor = this.colorRed;
+ this.velocity = Vector2.zero;
+ this.position = Vector2.zero;
+ this.origin = Vector2.zero;
+ this.rotation = 0;
+ this.visible = true;
+}
+
+Object.defineProperty(ThreeColorGameObject.prototype, "color",
+ {
+ get: function () {
+ if (this.currentColor === this.colorRed)
+ return Color.red;
+ else if (this.currentColor === this.colorGreen)
+ return Color.green;
+ else
+ return Color.blue;
+ },
+ set: function (value) {
+ if (value === Color.red)
+ this.currentColor = this.colorRed;
+ else if (value === Color.green)
+ this.currentColor = this.colorGreen;
+ else if (value === Color.blue)
+ this.currentColor = this.colorBlue;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.currentColor.width;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.currentColor.height;
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width, this.currentColor.height);
+ }
+ });
+
+Object.defineProperty(ThreeColorGameObject.prototype, "center",
+ {
+ get: function () {
+ return new Vector2(this.currentColor.width / 2, this.currentColor.height / 2);
+ }
+ });
+
+ThreeColorGameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+ThreeColorGameObject.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawImage(this.currentColor, this.position, this.rotation, 1, this.origin);
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/game-layout.css b/13_AdaptingDevices/PainterWithTouch/game-layout.css
new file mode 100644
index 0000000..b8e98c2
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/game-layout.css
@@ -0,0 +1,3 @@
+html, body {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/geom/Rectangle.js b/13_AdaptingDevices/PainterWithTouch/geom/Rectangle.js
new file mode 100644
index 0000000..424d189
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/geom/Rectangle.js
@@ -0,0 +1,63 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/geom/Vector2.js b/13_AdaptingDevices/PainterWithTouch/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/input/ButtonState.js b/13_AdaptingDevices/PainterWithTouch/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/input/Keyboard.js b/13_AdaptingDevices/PainterWithTouch/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/13_AdaptingDevices/PainterWithTouch/input/Mouse.js b/13_AdaptingDevices/PainterWithTouch/input/Mouse.js
new file mode 100644
index 0000000..b751735
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/input/Mouse.js
@@ -0,0 +1,88 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ Mouse._position = new Vector2(evt.pageX, evt.pageY);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/13_AdaptingDevices/PainterWithTouch/input/Touch.js b/13_AdaptingDevices/PainterWithTouch/input/Touch.js
new file mode 100644
index 0000000..cb8a2d5
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/input/Touch.js
@@ -0,0 +1,122 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isPressing",
+ {
+ get: function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ if (this._touchPresses[i]) {
+ return true;
+ }
+ return false;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var mx = this._touches[index].pageX;
+ var my = this._touches[index].pageY;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.isValid = function (index) {
+ return index >= 0 && index < this._touches.length;
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/system/Color.js b/13_AdaptingDevices/PainterWithTouch/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/13_AdaptingDevices/PainterWithTouch/system/Keys.js b/13_AdaptingDevices/PainterWithTouch/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/13_AdaptingDevices/PainterWithTouch/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/Canvas2D.js b/14_GameObjectStructure/JewelJam2/Canvas2D.js
new file mode 100644
index 0000000..f9ed84b
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/Canvas2D.js
@@ -0,0 +1,118 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+ window.onresize = Canvas2D_Singleton.prototype.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x * scale, -origin.y * scale,
+ sourceRect.width * scale, sourceRect.height * scale);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
+
diff --git a/14_GameObjectStructure/JewelJam2/Game.js b/14_GameObjectStructure/JewelJam2/Game.js
new file mode 100644
index 0000000..bab8420
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/Game.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ this.gameWorld = null;
+
+ // for debugging
+ this.log = "";
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ this._totalSprites += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ // displaying the number of touches
+ Canvas2D.drawText(Game.log);
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/JewelJam.html b/14_GameObjectStructure/JewelJam2/JewelJam.html
new file mode 100644
index 0000000..583144d
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/JewelJam.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ Jewel Jam
+
+
+
+ Jewel Jam
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/JewelJam.js b/14_GameObjectStructure/JewelJam2/JewelJam.js
new file mode 100644
index 0000000..df54f85
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/JewelJam.js
@@ -0,0 +1,30 @@
+"use strict";
+
+var sprites = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/JewelJam/sprites/" + sprite);
+ };
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.single_jewel1 = loadSprite("spr_single_jewel1.png");
+ sprites.single_jewel2 = loadSprite("spr_single_jewel2.png");
+ sprites.single_jewel3 = loadSprite("spr_single_jewel3.png");
+};
+
+Game.initialize = function () {
+ Game.gameWorld = new JewelJamGameWorld();
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/14_GameObjectStructure/JewelJam2/JewelJamGameWorld.js b/14_GameObjectStructure/JewelJam2/JewelJamGameWorld.js
new file mode 100644
index 0000000..09ec94f
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/JewelJamGameWorld.js
@@ -0,0 +1,52 @@
+"use strict";
+
+function JewelJamGameWorld() {
+
+ this.rows = 10;
+ this.columns = 5;
+ this.grid = new Array(this.rows * this.columns);
+ for (var i = 0; i < this.rows * this.columns; i++) {
+ var randomval = Math.floor(Math.random() * 3) + 1;
+ this.grid[i] = sprites["single_jewel" + randomval];
+ }
+}
+
+JewelJamGameWorld.prototype.setGridValue = function (x, y, value) {
+ var index = y * this.columns + x;
+ this.grid[index] = value;
+};
+
+JewelJamGameWorld.prototype.getGridValue = function (x, y) {
+ var index = y * this.columns + x;
+ return this.grid[index];
+};
+
+JewelJamGameWorld.prototype.moveRowDown = function () {
+ for (var y = this.rows - 2; y >= 0; y--) {
+ for (var x = 0; x < this.columns; x++) {
+ this.setGridValue(x, y + 1, this.getGridValue(x, y));
+ }
+ }
+ for (x = 0; x < this.columns; x++) {
+ var randomval = Math.floor(Math.random() * 3) + 1;
+ this.setGridValue(x, 0, sprites["single_jewel" + randomval]);
+ }
+};
+
+JewelJamGameWorld.prototype.handleInput = function (delta) {
+ if (Keyboard.pressed(Keys.space))
+ this.moveRowDown();
+};
+
+JewelJamGameWorld.prototype.update = function (delta) {
+};
+
+JewelJamGameWorld.prototype.draw = function (delta) {
+ Canvas2D.drawImage(sprites.background);
+ for (var row = 0; row < this.rows; row++) {
+ for (var col = 0; col < this.columns; col++) {
+ var position = new Vector2(85 + col * 85, 150 + row * 85);
+ Canvas2D.drawImage(this.getGridValue(col, row), position);
+ }
+ }
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/Sound.js b/14_GameObjectStructure/JewelJam2/Sound.js
new file mode 100644
index 0000000..e4b7afe
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function(value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function() {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/game-layout.css b/14_GameObjectStructure/JewelJam2/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/geom/Rectangle.js b/14_GameObjectStructure/JewelJam2/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/geom/Vector2.js b/14_GameObjectStructure/JewelJam2/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/input/ButtonState.js b/14_GameObjectStructure/JewelJam2/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/input/Keyboard.js b/14_GameObjectStructure/JewelJam2/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/14_GameObjectStructure/JewelJam2/input/Mouse.js b/14_GameObjectStructure/JewelJam2/input/Mouse.js
new file mode 100644
index 0000000..22bc378
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/14_GameObjectStructure/JewelJam2/input/Touch.js b/14_GameObjectStructure/JewelJam2/input/Touch.js
new file mode 100644
index 0000000..fea1e09
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/input/Touch.js
@@ -0,0 +1,124 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isPressing",
+ {
+ get: function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ if (this._touchPresses[i]) {
+ return true;
+ }
+ return false;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.isValid = function (index) {
+ return index >= 0 && index < this._touches.length;
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/system/Color.js b/14_GameObjectStructure/JewelJam2/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam2/system/Keys.js b/14_GameObjectStructure/JewelJam2/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam2/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/Canvas2D.js b/14_GameObjectStructure/JewelJam3/Canvas2D.js
new file mode 100644
index 0000000..f9ed84b
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/Canvas2D.js
@@ -0,0 +1,118 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+ window.onresize = Canvas2D_Singleton.prototype.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x * scale, -origin.y * scale,
+ sourceRect.width * scale, sourceRect.height * scale);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
+
diff --git a/14_GameObjectStructure/JewelJam3/Game.js b/14_GameObjectStructure/JewelJam3/Game.js
new file mode 100644
index 0000000..bab8420
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/Game.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ this.gameWorld = null;
+
+ // for debugging
+ this.log = "";
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ this._totalSprites += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ // displaying the number of touches
+ Canvas2D.drawText(Game.log);
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/JewelJam.html b/14_GameObjectStructure/JewelJam3/JewelJam.html
new file mode 100644
index 0000000..0c11cbc
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/JewelJam.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+ Jewel Jam
+
+
+
+ Jewel Jam
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/JewelJam.js b/14_GameObjectStructure/JewelJam3/JewelJam.js
new file mode 100644
index 0000000..62df32f
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/JewelJam.js
@@ -0,0 +1,42 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/JewelJam/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/JewelJam/sounds/" + sound, looping);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.single_jewel1 = loadSprite("spr_single_jewel1.png");
+ sprites.single_jewel2 = loadSprite("spr_single_jewel2.png");
+ sprites.single_jewel3 = loadSprite("spr_single_jewel3.png");
+};
+
+Game.initialize = function () {
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_objects = 20;
+
+ // create the game world
+ Game.gameWorld = new JewelJamGameWorld();
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/14_GameObjectStructure/JewelJam3/Sound.js b/14_GameObjectStructure/JewelJam3/Sound.js
new file mode 100644
index 0000000..e4b7afe
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function(value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function() {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/game-layout.css b/14_GameObjectStructure/JewelJam3/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/gameobjects/GameObject.js b/14_GameObjectStructure/JewelJam3/gameobjects/GameObject.js
new file mode 100644
index 0000000..cd0abfc
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/gameobjects/GameObject.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function GameObject(layer) {
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.handleInput = function (delta) {
+};
+
+GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+GameObject.prototype.draw = function () {
+};
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
diff --git a/14_GameObjectStructure/JewelJam3/gameobjects/GameObjectGrid.js b/14_GameObjectStructure/JewelJam3/gameobjects/GameObjectGrid.js
new file mode 100644
index 0000000..87e9f3f
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/gameobjects/GameObjectGrid.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function GameObjectGrid(rows, columns, layer) {
+ GameObjectList.call(this, layer);
+
+ this.cellWidth = 0;
+ this.cellHeight = 0;
+ this._rows = rows;
+ this._columns = columns;
+}
+
+GameObjectGrid.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(GameObjectGrid.prototype, "rows", {
+ get: function () {
+ return this._rows;
+ }
+});
+
+Object.defineProperty(GameObjectGrid.prototype, "columns", {
+ get: function () {
+ return this._columns;
+ }
+});
+
+GameObjectGrid.prototype.add = function (gameobject) {
+ var row = Math.floor(this._gameObjects.length / this._columns);
+ var col = this._gameObjects.length % this._columns;
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.addAt = function (gameobject, col, row) {
+ this._gameObjects[row * this._columns + col] = gameobject;
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.at = function (col, row) {
+ var index = row * this._columns + col;
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ else
+ return this._gameObjects[index];
+};
+
+GameObjectGrid.prototype.getAnchorPosition = function (gameobject) {
+ var l = this._gameObjects.length;
+ for (var i = 0; i < l; ++i)
+ if (this._gameObjects[i] === gameobject) {
+ var row = Math.floor(i / this.columns);
+ var col = i % this.columns;
+ return new Vector2(col * this.cellWidth, row * this.cellHeight);
+ }
+ return Vector2.zero;
+};
diff --git a/14_GameObjectStructure/JewelJam3/gameobjects/GameObjectList.js b/14_GameObjectStructure/JewelJam3/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..74047c3
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/gameobjects/GameObjectList.js
@@ -0,0 +1,68 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer) {
+ GameObject.call(this, layer);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
diff --git a/14_GameObjectStructure/JewelJam3/gameobjects/Jewel.js b/14_GameObjectStructure/JewelJam3/gameobjects/Jewel.js
new file mode 100644
index 0000000..a07b90c
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/gameobjects/Jewel.js
@@ -0,0 +1,18 @@
+"use strict";
+
+function Jewel(layer) {
+ var randomval = Math.floor(Math.random() * 3) + 1;
+ var spr = sprites["single_jewel" + randomval];
+ SpriteGameObject.call(this, spr, layer);
+}
+
+Jewel.prototype = Object.create(SpriteGameObject.prototype);
+
+Jewel.prototype.update = function (delta) {
+ SpriteGameObject.prototype.update.call(this, delta);
+
+ if (this.parent.dragging)
+ return;
+ var anchor = this.parent.getAnchorPosition(this);
+ this.velocity = anchor.subtractFrom(this.position).multiplyWith(15);
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/gameobjects/JewelGrid.js b/14_GameObjectStructure/JewelJam3/gameobjects/JewelGrid.js
new file mode 100644
index 0000000..77c8d64
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/gameobjects/JewelGrid.js
@@ -0,0 +1,105 @@
+"use strict";
+
+function JewelGrid(rows, columns, layer) {
+ GameObjectGrid.call(this, rows, columns, layer);
+ this.dragging = false;
+ this._dragRow = 0;
+ this._draggingLastX = 0;
+ this._touchIndex = 0;
+}
+
+JewelGrid.prototype = Object.create(GameObjectGrid.prototype);
+
+JewelGrid.prototype.handleInput = function (delta) {
+ if (Touch.isTouchDevice)
+ this.handleInputTouch(delta);
+ else
+ this.handleInputMouse(delta);
+};
+
+JewelGrid.prototype.handleInputMouse = function (delta) {
+ if (Mouse.left.down && !this.dragging) {
+ var rect = new Rectangle(this.worldPosition.x, this.worldPosition.y, this.columns * this.cellHeight, this.rows * this.cellWidth);
+ if (Mouse.containsMouseDown(rect)) {
+ this.dragging = true;
+ this._dragRow = Math.floor((Mouse.position.y - this.worldPosition.y) / this.cellHeight);
+ this._draggingLastX = Mouse.position.x - this.worldPosition.x;
+ }
+ }
+ if (!Mouse.left.down) {
+ this.dragging = false;
+ }
+
+ if (this.dragging) {
+ var newpos = Mouse.position.x - this.worldPosition.x;
+
+ // reposition each jewel in the row
+ for (var i = 0; i < this.columns; i++) {
+ var currObj = this.at(i, this._dragRow);
+ currObj.position.x += (newpos - this._draggingLastX);
+ }
+ // check if we need to shift a row
+ var firstObj = this.at(0, this._dragRow);
+ if (firstObj.position.x < -this.cellWidth / 2 && newpos - this._draggingLastX < 0)
+ this.shiftRowLeft(this._dragRow);
+ var lastObj = this.at(this.columns - 1, this._dragRow);
+ if (lastObj.position.x > (this.columns - 1) * this.cellWidth + this.cellWidth / 2 && newpos - this._draggingLastX > 0)
+ this.shiftRowRight(this._dragRow);
+ this._draggingLastX = newpos;
+ }
+};
+
+JewelGrid.prototype.handleInputTouch = function (delta) {
+ var pos, newpos;
+ var rect = new Rectangle(this.worldPosition.x, this.worldPosition.y, this.columns * this.cellHeight, this.rows * this.cellWidth);
+
+ if (Touch.isTouching && !this.dragging) {
+ if (Touch.containsTouch(rect)) {
+ this._touchIndex = Touch.getIndexInRect(rect);
+ pos = Touch.getPosition(this._touchIndex);
+ this.dragging = true;
+ this._dragRow = Math.floor((pos.y - this.worldPosition.y) / this.cellHeight);
+ this._draggingLastX = pos.x - this.worldPosition.x;
+ }
+ }
+ if (!Touch.isTouching) {
+ this.dragging = false;
+ }
+
+ if (this.dragging) {
+ pos = Touch.getPosition(this._touchIndex);
+ newpos = pos.x - this.worldPosition.x;
+
+ // reposition each jewel in the row
+ for (var i = 0; i < this.columns; i++) {
+ var currObj = this.at(i, this._dragRow);
+ currObj.position.x += (newpos - this._draggingLastX);
+ }
+ // check if we need to shift a row
+ var firstObj = this.at(0, this._dragRow);
+ if (firstObj.position.x < -this.cellWidth / 2 && newpos - this._draggingLastX < 0)
+ this.shiftRowLeft(this._dragRow);
+ var lastObj = this.at(this.columns - 1, this._dragRow);
+ if (lastObj.position.x > (this.columns - 1) * this.cellWidth + this.cellWidth / 2 && newpos - this._draggingLastX > 0)
+ this.shiftRowRight(this._dragRow);
+ this._draggingLastX = newpos;
+ }
+};
+
+JewelGrid.prototype.shiftRowRight = function (selectedRow) {
+ var lastObj = this.at(this._columns - 1, selectedRow);
+ var positionOffset = lastObj.position.x - (this.columns - 1) * this.cellWidth;
+ for (var x = this._columns - 1; x > 0; x -= 1)
+ this._gameObjects[selectedRow * this._columns + x] = this._gameObjects[selectedRow * this._columns + (x - 1)];
+ this._gameObjects[selectedRow * this._columns] = lastObj;
+ lastObj.position = new Vector2(-this.cellWidth + positionOffset, selectedRow * this.cellHeight);
+};
+
+JewelGrid.prototype.shiftRowLeft = function (selectedRow) {
+ var firstObj = this.at(0, selectedRow);
+ var positionOffset = firstObj.position.x;
+ for (var x = 0; x < this._columns - 1; x += 1)
+ this._gameObjects[selectedRow * this._columns + x] = this._gameObjects[selectedRow * this._columns + x + 1];
+ this._gameObjects[selectedRow * this._columns + (this._columns - 1)] = firstObj;
+ firstObj.position = new Vector2(this._columns * this.cellWidth + positionOffset, selectedRow * this.cellHeight);
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/gameobjects/JewelJamGameWorld.js b/14_GameObjectStructure/JewelJam3/gameobjects/JewelJamGameWorld.js
new file mode 100644
index 0000000..9c3c27d
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/gameobjects/JewelJamGameWorld.js
@@ -0,0 +1,19 @@
+"use strict";
+
+function JewelJamGameWorld(layer) {
+ GameObjectList.call(this, layer);
+
+ this.add(new SpriteGameObject(sprites.background, ID.layer_background));
+
+ var rows = 10, columns = 5;
+ var grid = new JewelGrid(rows, columns, ID.layer_objects);
+ grid.position = new Vector2(85, 150);
+ grid.cellWidth = 85;
+ grid.cellHeight = 85;
+ this.add(grid);
+ for (var i = 0; i < rows * columns; i++) {
+ grid.add(new Jewel());
+ }
+}
+
+JewelJamGameWorld.prototype = Object.create(GameObjectList.prototype);
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/gameobjects/SpriteGameObject.js b/14_GameObjectStructure/JewelJam3/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..dc4c523
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/gameobjects/SpriteGameObject.js
@@ -0,0 +1,36 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer) {
+ GameObject.call(this, layer);
+ this.sprite = sprite;
+ this.origin = Vector2.zero;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawImage(this.sprite, this.worldPosition, 0, 1, this.origin);
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/geom/Rectangle.js b/14_GameObjectStructure/JewelJam3/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/geom/Vector2.js b/14_GameObjectStructure/JewelJam3/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/input/ButtonState.js b/14_GameObjectStructure/JewelJam3/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/input/Keyboard.js b/14_GameObjectStructure/JewelJam3/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/14_GameObjectStructure/JewelJam3/input/Mouse.js b/14_GameObjectStructure/JewelJam3/input/Mouse.js
new file mode 100644
index 0000000..22bc378
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/14_GameObjectStructure/JewelJam3/input/Touch.js b/14_GameObjectStructure/JewelJam3/input/Touch.js
new file mode 100644
index 0000000..fea1e09
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/input/Touch.js
@@ -0,0 +1,124 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isPressing",
+ {
+ get: function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ if (this._touchPresses[i]) {
+ return true;
+ }
+ return false;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.isValid = function (index) {
+ return index >= 0 && index < this._touches.length;
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/system/Color.js b/14_GameObjectStructure/JewelJam3/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/JewelJam3/system/Keys.js b/14_GameObjectStructure/JewelJam3/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/14_GameObjectStructure/JewelJam3/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/14_GameObjectStructure/LAB.min.js b/14_GameObjectStructure/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/14_GameObjectStructure/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/Canvas2D.js b/15_GameplayProgramming/JewelJam4/Canvas2D.js
new file mode 100644
index 0000000..f9ed84b
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/Canvas2D.js
@@ -0,0 +1,118 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+ window.onresize = Canvas2D_Singleton.prototype.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x * scale, -origin.y * scale,
+ sourceRect.width * scale, sourceRect.height * scale);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
+
diff --git a/15_GameplayProgramming/JewelJam4/Game.js b/15_GameplayProgramming/JewelJam4/Game.js
new file mode 100644
index 0000000..bab8420
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/Game.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ this.gameWorld = null;
+
+ // for debugging
+ this.log = "";
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ this._totalSprites += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ // displaying the number of touches
+ Canvas2D.drawText(Game.log);
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/JewelJam.html b/15_GameplayProgramming/JewelJam4/JewelJam.html
new file mode 100644
index 0000000..d9537ff
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/JewelJam.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ Jewel Jam
+
+
+
+ Jewel Jam
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/JewelJam.js b/15_GameplayProgramming/JewelJam4/JewelJam.js
new file mode 100644
index 0000000..6a16735
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/JewelJam.js
@@ -0,0 +1,71 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/JewelJam/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/JewelJam/sounds/" + sound, looping);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.frame_score = loadSprite("spr_frame_score.jpg");
+ sprites.button_help = loadSprite("spr_button_help.jpg");
+ sprites.frame_help = loadSprite("spr_frame_help.png");
+ sprites.jewelcart = loadSprite("spr_jewelcart.png");
+ sprites.jewels = loadSprite("spr_jewels@27.png");
+ if (Touch.isTouchDevice) {
+ sprites.gameover = loadSprite("spr_gameover_tap.png");
+ sprites.title = loadSprite("spr_title_tap.jpg");
+ } else {
+ sprites.gameover = loadSprite("spr_gameover_click.png");
+ sprites.title = loadSprite("spr_title_click.jpg");
+ }
+};
+
+Game.initialize = function () {
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.title = 1;
+ ID.help_frame = 2;
+ ID.jewel_cart = 3;
+ ID.grid = 4;
+ ID.game_over = 5;
+ ID.score = 6;
+ ID.double_timer = 7;
+ ID.triple_timer = 8;
+
+ // create the game world
+ Game.gameWorld = new JewelJamGameWorld();
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/15_GameplayProgramming/JewelJam4/Sound.js b/15_GameplayProgramming/JewelJam4/Sound.js
new file mode 100644
index 0000000..e4b7afe
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function(value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function() {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/game-layout.css b/15_GameplayProgramming/JewelJam4/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/GameObject.js b/15_GameplayProgramming/JewelJam4/gameobjects/GameObject.js
new file mode 100644
index 0000000..c211ee3
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/GameObject.js
@@ -0,0 +1,59 @@
+"use strict";
+
+function GameObject(layer, id) {
+
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
+
+GameObject.prototype.handleInput = function (delta) {
+};
+
+GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+GameObject.prototype.draw = function () {
+};
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/GameObjectGrid.js b/15_GameplayProgramming/JewelJam4/gameobjects/GameObjectGrid.js
new file mode 100644
index 0000000..e79c4ed
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/GameObjectGrid.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function GameObjectGrid(rows, columns, layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.cellWidth = 0;
+ this.cellHeight = 0;
+ this._rows = rows;
+ this._columns = columns;
+}
+
+GameObjectGrid.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(GameObjectGrid.prototype, "rows", {
+ get: function () {
+ return this._rows;
+ }
+});
+
+Object.defineProperty(GameObjectGrid.prototype, "columns", {
+ get: function () {
+ return this._columns;
+ }
+});
+
+GameObjectGrid.prototype.add = function (gameobject) {
+ var row = Math.floor(this._gameObjects.length / this._columns);
+ var col = this._gameObjects.length % this._columns;
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.addAt = function (gameobject, col, row) {
+ this._gameObjects[row * this._columns + col] = gameobject;
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.at = function (col, row) {
+ var index = row * this._columns + col;
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ else
+ return this._gameObjects[index];
+};
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/GameObjectList.js b/15_GameplayProgramming/JewelJam4/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..c595ced
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/GameObjectList.js
@@ -0,0 +1,81 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer, id) {
+ GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/Jewel.js b/15_GameplayProgramming/JewelJam4/gameobjects/Jewel.js
new file mode 100644
index 0000000..40e5a88
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/Jewel.js
@@ -0,0 +1,25 @@
+"use strict";
+
+function Jewel(layer, id) {
+ SpriteGameObject.call(this, sprites.jewels, layer, id);
+ this.variation = Math.floor(Math.random() * 27);
+}
+
+Jewel.prototype = Object.create(SpriteGameObject.prototype);
+
+Jewel.prototype.update = function (delta) {
+ SpriteGameObject.prototype.update.call(this, delta);
+
+ if (this.parent.dragging)
+ return;
+ var anchor = this.parent.getAnchorPosition(this);
+ this.velocity = anchor.subtractFrom(this.position).multiplyWith(15);
+};
+
+Jewel.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ var imagePart = new Rectangle(this.variation * this.height, 0,
+ this.height, this.height);
+ Canvas2D.drawImage(this.sprite, this.worldPosition, 0, 1, this.origin, imagePart);
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/JewelCart.js b/15_GameplayProgramming/JewelJam4/gameobjects/JewelCart.js
new file mode 100644
index 0000000..693596e
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/JewelCart.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function JewelCart(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+ this.push = 50;
+ this.minxpos = 0;
+ this.velocity = new Vector2(10, 0);
+}
+
+JewelCart.prototype = Object.create(SpriteGameObject.prototype);
+
+JewelCart.prototype.update = function (delta) {
+ SpriteGameObject.prototype.update.call(this, delta);
+ this.push = Math.max(this.push - 0.00001, 1);
+};
+
+JewelCart.prototype.reset = function () {
+ SpriteGameObject.prototype.reset.call(this);
+ this.position.x = this.minxpos;
+};
+
+JewelCart.prototype.pushCart = function () {
+ this.position.x = Math.max(this.position.x - this.push, this.minxpos);
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/JewelGrid.js b/15_GameplayProgramming/JewelJam4/gameobjects/JewelGrid.js
new file mode 100644
index 0000000..930c117
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/JewelGrid.js
@@ -0,0 +1,174 @@
+"use strict";
+
+function JewelGrid(rows, columns, layer, id) {
+ GameObjectGrid.call(this, rows, columns, layer, id);
+ this.dragging = false;
+ this._dragRow = 0;
+ this._draggingLastX = 0;
+ this._touchIndex = 0;
+}
+
+JewelGrid.prototype = Object.create(GameObjectGrid.prototype);
+
+JewelGrid.prototype.handleInput = function (delta) {
+ if (Touch.isTouchDevice)
+ this.handleInputTouch(delta);
+ else
+ this.handleInputMouse(delta);
+};
+
+JewelGrid.prototype.handleInputMouse = function (delta) {
+ if (Mouse.left.down && !this.dragging) {
+ var rect = new Rectangle(this.worldPosition.x, this.worldPosition.y, this.columns * this.cellHeight, this.rows * this.cellWidth);
+ if (Mouse.containsMouseDown(rect)) {
+ this.dragging = true;
+ this._dragRow = Math.floor((Mouse.position.y - this.worldPosition.y) / this.cellHeight);
+ this._draggingLastX = Mouse.position.x - this.worldPosition.x;
+ }
+ }
+ if (!Mouse.left.down) {
+ this.dragging = false;
+ }
+
+ if (this.dragging) {
+ var newpos = Mouse.position.x - this.worldPosition.x;
+
+ // reposition each jewel in the row
+ for (var i = 0; i < this.columns; i++) {
+ var currObj = this.at(i, this._dragRow);
+ currObj.position.x += (newpos - this._draggingLastX);
+ }
+ // check if we need to shift a row
+ var firstObj = this.at(0, this._dragRow);
+ if (firstObj.position.x < -this.cellWidth / 2 && newpos - this._draggingLastX < 0)
+ this.shiftRowLeft(this._dragRow);
+ var lastObj = this.at(this.columns - 1, this._dragRow);
+ if (lastObj.position.x > (this.columns - 1) * this.cellWidth + this.cellWidth / 2 && newpos - this._draggingLastX > 0)
+ this.shiftRowRight(this._dragRow);
+ this._draggingLastX = newpos;
+ }
+};
+
+JewelGrid.prototype.handleInputTouch = function (delta) {
+ var pos, newpos;
+ var rect = new Rectangle(this.worldPosition.x, this.worldPosition.y, this.columns * this.cellHeight, this.rows * this.cellWidth);
+
+ if (Touch.isTouching && !this.dragging) {
+ if (Touch.containsTouch(rect)) {
+ this._touchIndex = Touch.getIndexInRect(rect);
+ pos = Touch.getPosition(this._touchIndex);
+ this.dragging = true;
+ this._dragRow = Math.floor((pos.y - this.worldPosition.y) / this.cellHeight);
+ this._draggingLastX = pos.x - this.worldPosition.x;
+ }
+ }
+ if (!Touch.isTouching) {
+ this.dragging = false;
+ }
+
+ if (this.dragging) {
+ pos = Touch.getPosition(this._touchIndex);
+ newpos = pos.x - this.worldPosition.x;
+
+ // reposition each jewel in the row
+ for (var i = 0; i < this.columns; i++) {
+ var currObj = this.at(i, this._dragRow);
+ currObj.position.x += (newpos - this._draggingLastX);
+ }
+ // check if we need to shift a row
+ var firstObj = this.at(0, this._dragRow);
+ if (firstObj.position.x < -this.cellWidth / 2 && newpos - this._draggingLastX < 0)
+ this.shiftRowLeft(this._dragRow);
+ var lastObj = this.at(this.columns - 1, this._dragRow);
+ if (lastObj.position.x > (this.columns - 1) * this.cellWidth + this.cellWidth / 2 && newpos - this._draggingLastX > 0)
+ this.shiftRowRight(this._dragRow);
+ this._draggingLastX = newpos;
+ }
+};
+
+JewelGrid.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ if (this.dragging)
+ return;
+
+ var middleCol = Math.floor(this._columns / 2);
+ var i = 0;
+ var score = this.root.find(ID.score);
+ while (i < this._rows - 2) {
+ if (this.isValidCombination(this.at(middleCol, i),
+ this.at(middleCol, i + 1),
+ this.at(middleCol, i + 2))) {
+ this.removeJewel(middleCol, i, -this.cellHeight);
+ this.removeJewel(middleCol, i + 1, -this.cellHeight * 2);
+ this.removeJewel(middleCol, i + 2, -this.cellHeight * 3);
+ score.score += 10;
+
+ var jewelCart = this.root.find(ID.jewel_cart);
+ jewelCart.pushCart();
+
+ i = 0;
+ }
+ else
+ i++;
+ }
+};
+
+JewelGrid.prototype.isValidCombination = function (a, b, c) {
+ var curra = a.variation;
+ var currb = b.variation;
+ var currc = c.variation;
+ var divider = 9;
+
+ for (var i = 0; i < 3; i += 1) {
+ if ((Math.floor(curra / divider) + Math.floor(currb / divider) + Math.floor(currc / divider)) % 3 !== 0)
+ return false;
+ curra = curra % divider;
+ currb = currb % divider;
+ currc = currc % divider;
+ divider = Math.floor(divider / 3);
+ }
+ return true;
+};
+
+JewelGrid.prototype.reset = function () {
+ this.clear();
+ for (var i = 0; i < this._rows * this._columns; i += 1)
+ this.add(new Jewel(ID.layer_objects));
+};
+
+JewelGrid.prototype.removeJewel = function (x, y, newYPosition) {
+ for (var row = y; row > 0; row -= 1)
+ this._gameObjects[row * this._columns + x] = this._gameObjects[(row - 1) * this._columns + x];
+ var jewel = new Jewel();
+ this.addAt(jewel, x, 0);
+ jewel.position.y = newYPosition;
+};
+
+JewelGrid.prototype.shiftRowRight = function (selectedRow) {
+ var lastObj = this.at(this._columns - 1, selectedRow);
+ var positionOffset = lastObj.position.x - (this.columns - 1) * this.cellWidth;
+ for (var x = this._columns - 1; x > 0; x -= 1)
+ this._gameObjects[selectedRow * this._columns + x] = this._gameObjects[selectedRow * this._columns + (x - 1)];
+ this._gameObjects[selectedRow * this._columns] = lastObj;
+ lastObj.position = new Vector2(-this.cellWidth + positionOffset, selectedRow * this.cellHeight);
+};
+
+JewelGrid.prototype.shiftRowLeft = function (selectedRow) {
+ var firstObj = this.at(0, selectedRow);
+ var positionOffset = firstObj.position.x;
+ for (var x = 0; x < this._columns - 1; x += 1)
+ this._gameObjects[selectedRow * this._columns + x] = this._gameObjects[selectedRow * this._columns + x + 1];
+ this._gameObjects[selectedRow * this._columns + (this._columns - 1)] = firstObj;
+ firstObj.position = new Vector2(this._columns * this.cellWidth + positionOffset, selectedRow * this.cellHeight);
+};
+
+JewelGrid.prototype.getAnchorPosition = function (gameobject) {
+ var l = this._gameObjects.length;
+ for (var i = 0; i < l; ++i)
+ if (this._gameObjects[i] == gameobject) {
+ var row = Math.floor(i / this.columns);
+ var col = i - row * this.columns;
+ return new Vector2(col * this.cellWidth, row * this.cellHeight);
+ }
+ return Vector2.zero;
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/JewelJamGameWorld.js b/15_GameplayProgramming/JewelJam4/gameobjects/JewelJamGameWorld.js
new file mode 100644
index 0000000..f9276ab
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/JewelJamGameWorld.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function JewelJamGameWorld(layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ // add a background sprite
+ this.add(new SpriteGameObject(sprites.background, ID.layer_background));
+
+ // add a score frame
+ var scoreFrame = new SpriteGameObject(sprites.frame_score, ID.layer_overlays);
+ scoreFrame.position = new Vector2(20, 20);
+ this.add(scoreFrame);
+
+ var score = new ScoreGameObject("Segoe UI Mono", "40px", ID.layer_overlays_1, ID.score);
+ score.position = new Vector2(270, 35);
+ score.color = Color.white;
+
+ this.add(score);
+
+ var jewelCart = new JewelCart(sprites.jewelcart, ID.layer_objects, ID.jewel_cart);
+ jewelCart.position = new Vector2(410, 230);
+ jewelCart.minxpos = 410;
+ this.add(jewelCart);
+
+ // add the grid
+ var rows = 10, columns = 5;
+ var grid = new JewelGrid(rows, columns, ID.layer_objects, ID.grid);
+ grid.position = new Vector2(85, 150);
+ grid.cellWidth = 85;
+ grid.cellHeight = 85;
+ grid.reset();
+ this.add(grid);
+}
+
+JewelJamGameWorld.prototype = Object.create(GameObjectList.prototype);
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/ScoreGameObject.js b/15_GameplayProgramming/JewelJam4/gameobjects/ScoreGameObject.js
new file mode 100644
index 0000000..a543568
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/ScoreGameObject.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function ScoreGameObject(fontName, fontSize, layer, id) {
+ Label.call(this, fontName, fontSize, layer, id);
+ this.text = 0;
+ this._align = "right";
+}
+
+ScoreGameObject.prototype = Object.create(Label.prototype);
+
+Object.defineProperty(ScoreGameObject.prototype, "score",
+ {
+ get: function () {
+ return this._contents;
+ },
+ set: function (value) {
+ if (value >= 0)
+ this.text = value;
+ }
+ });
+
+ScoreGameObject.prototype.reset = function () {
+ this.text = 0;
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/gameobjects/SpriteGameObject.js b/15_GameplayProgramming/JewelJam4/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..60d9b69
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gameobjects/SpriteGameObject.js
@@ -0,0 +1,36 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer, id) {
+ GameObject.call(this, layer, id);
+ this.sprite = sprite;
+ this.origin = Vector2.zero;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawImage(this.sprite, this.worldPosition, 0, 1, this.origin);
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/geom/Rectangle.js b/15_GameplayProgramming/JewelJam4/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/geom/Vector2.js b/15_GameplayProgramming/JewelJam4/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/gui/Label.js b/15_GameplayProgramming/JewelJam4/gui/Label.js
new file mode 100644
index 0000000..41c5af6
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/gui/Label.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function calculateTextSize(fontname, fontsize, text) {
+ var div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = -1000;
+ div.style.top = -1000;
+ document.body.appendChild(div);
+ text = typeof text !== 'undefined' ? text : "M";
+ div.style.fontSize = "" + fontsize;
+ div.style.fontFamily = fontname;
+ div.innerHTML = text;
+ var size = new Vector2(div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+ return size;
+}
+
+function Label(fontname, fontsize, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.color = Color.black;
+ this.origin = Vector2.zero;
+ this._fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ this._fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+ this._contents = "";
+ this._align = "left";
+ this._size = Vector2.zero;
+}
+
+Label.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(Label.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "width",
+ {
+ get: function () {
+ return this._size.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "height",
+ {
+ get: function () {
+ return this._size.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(Label.prototype, "text",
+ {
+ get: function () {
+ return this._contents;
+ },
+
+ set: function (value) {
+ this._contents = value;
+ this._size = calculateTextSize(this._fontname, this._fontsize, value);
+ }
+ });
+
+Label.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawText(this._contents, this.worldPosition,
+ this.origin, this.color, this._align,
+ this._fontname, this._fontsize);
+};
diff --git a/15_GameplayProgramming/JewelJam4/input/ButtonState.js b/15_GameplayProgramming/JewelJam4/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/input/Keyboard.js b/15_GameplayProgramming/JewelJam4/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/15_GameplayProgramming/JewelJam4/input/Mouse.js b/15_GameplayProgramming/JewelJam4/input/Mouse.js
new file mode 100644
index 0000000..22bc378
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/15_GameplayProgramming/JewelJam4/input/Touch.js b/15_GameplayProgramming/JewelJam4/input/Touch.js
new file mode 100644
index 0000000..fea1e09
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/input/Touch.js
@@ -0,0 +1,124 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isPressing",
+ {
+ get: function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ if (this._touchPresses[i]) {
+ return true;
+ }
+ return false;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.isValid = function (index) {
+ return index >= 0 && index < this._touches.length;
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/system/Color.js b/15_GameplayProgramming/JewelJam4/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/JewelJam4/system/Keys.js b/15_GameplayProgramming/JewelJam4/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/15_GameplayProgramming/JewelJam4/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/15_GameplayProgramming/LAB.min.js b/15_GameplayProgramming/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/15_GameplayProgramming/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/Canvas2D.js b/16_GameStates/JewelJam5/Canvas2D.js
new file mode 100644
index 0000000..f9ed84b
--- /dev/null
+++ b/16_GameStates/JewelJam5/Canvas2D.js
@@ -0,0 +1,118 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+ window.onresize = Canvas2D_Singleton.prototype.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x * scale, -origin.y * scale,
+ sourceRect.width * scale, sourceRect.height * scale);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
+
diff --git a/16_GameStates/JewelJam5/Game.js b/16_GameStates/JewelJam5/Game.js
new file mode 100644
index 0000000..bab8420
--- /dev/null
+++ b/16_GameStates/JewelJam5/Game.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ this.gameWorld = null;
+
+ // for debugging
+ this.log = "";
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ this._totalSprites += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ // displaying the number of touches
+ Canvas2D.drawText(Game.log);
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/JewelJam.html b/16_GameStates/JewelJam5/JewelJam.html
new file mode 100644
index 0000000..f09f3b1
--- /dev/null
+++ b/16_GameStates/JewelJam5/JewelJam.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ Jewel Jam
+
+
+
+ Jewel Jam
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/JewelJam.js b/16_GameStates/JewelJam5/JewelJam.js
new file mode 100644
index 0000000..07a4965
--- /dev/null
+++ b/16_GameStates/JewelJam5/JewelJam.js
@@ -0,0 +1,75 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/JewelJam/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/JewelJam/sounds/" + sound, looping);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.frame_score = loadSprite("spr_frame_score.jpg");
+ sprites.button_help = loadSprite("spr_button_help.jpg");
+ sprites.frame_help = loadSprite("spr_frame_help.png");
+ sprites.jewelcart = loadSprite("spr_jewelcart.png");
+ sprites.jewels = loadSprite("spr_jewels@27.png");
+ if (Touch.isTouchDevice) {
+ sprites.gameover = loadSprite("spr_gameover_tap.png");
+ sprites.title = loadSprite("spr_title_tap.jpg");
+ } else {
+ sprites.gameover = loadSprite("spr_gameover_click.png");
+ sprites.title = loadSprite("spr_title_click.jpg");
+ }
+ sprites.frame_selector = loadSprite("spr_frame_selector.png");
+ sprites.glitter = loadSprite("spr_glitter.png");
+ sprites.double = loadSprite("spr_double.png");
+ sprites.triple = loadSprite("spr_triple.png");
+};
+
+Game.initialize = function () {
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.title = 1;
+ ID.help_frame = 2;
+ ID.jewel_cart = 3;
+ ID.grid = 4;
+ ID.game_over = 5;
+ ID.score = 6;
+ ID.double_timer = 7;
+ ID.triple_timer = 8;
+
+ // create the game world
+ Game.gameWorld = new JewelJamGameWorld();
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/16_GameStates/JewelJam5/Sound.js b/16_GameStates/JewelJam5/Sound.js
new file mode 100644
index 0000000..e4b7afe
--- /dev/null
+++ b/16_GameStates/JewelJam5/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function(value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function() {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/game-layout.css b/16_GameStates/JewelJam5/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/16_GameStates/JewelJam5/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gameobjects/GameObject.js b/16_GameStates/JewelJam5/gameobjects/GameObject.js
new file mode 100644
index 0000000..589d431
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/GameObject.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function GameObject(layer, id) {
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
+
+GameObject.prototype.handleInput = function (delta) {
+};
+
+GameObject.prototype.update = function (delta) {
+ if (typeof this.position === 'undefined') {
+ console.log("oops...");
+ }
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+GameObject.prototype.draw = function () {
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gameobjects/GameObjectGrid.js b/16_GameStates/JewelJam5/gameobjects/GameObjectGrid.js
new file mode 100644
index 0000000..e79c4ed
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/GameObjectGrid.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function GameObjectGrid(rows, columns, layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.cellWidth = 0;
+ this.cellHeight = 0;
+ this._rows = rows;
+ this._columns = columns;
+}
+
+GameObjectGrid.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(GameObjectGrid.prototype, "rows", {
+ get: function () {
+ return this._rows;
+ }
+});
+
+Object.defineProperty(GameObjectGrid.prototype, "columns", {
+ get: function () {
+ return this._columns;
+ }
+});
+
+GameObjectGrid.prototype.add = function (gameobject) {
+ var row = Math.floor(this._gameObjects.length / this._columns);
+ var col = this._gameObjects.length % this._columns;
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.addAt = function (gameobject, col, row) {
+ this._gameObjects[row * this._columns + col] = gameobject;
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.at = function (col, row) {
+ var index = row * this._columns + col;
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ else
+ return this._gameObjects[index];
+};
diff --git a/16_GameStates/JewelJam5/gameobjects/GameObjectList.js b/16_GameStates/JewelJam5/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..c595ced
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/GameObjectList.js
@@ -0,0 +1,81 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer, id) {
+ GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
diff --git a/16_GameStates/JewelJam5/gameobjects/Jewel.js b/16_GameStates/JewelJam5/gameobjects/Jewel.js
new file mode 100644
index 0000000..40e5a88
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/Jewel.js
@@ -0,0 +1,25 @@
+"use strict";
+
+function Jewel(layer, id) {
+ SpriteGameObject.call(this, sprites.jewels, layer, id);
+ this.variation = Math.floor(Math.random() * 27);
+}
+
+Jewel.prototype = Object.create(SpriteGameObject.prototype);
+
+Jewel.prototype.update = function (delta) {
+ SpriteGameObject.prototype.update.call(this, delta);
+
+ if (this.parent.dragging)
+ return;
+ var anchor = this.parent.getAnchorPosition(this);
+ this.velocity = anchor.subtractFrom(this.position).multiplyWith(15);
+};
+
+Jewel.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ var imagePart = new Rectangle(this.variation * this.height, 0,
+ this.height, this.height);
+ Canvas2D.drawImage(this.sprite, this.worldPosition, 0, 1, this.origin, imagePart);
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gameobjects/JewelCart.js b/16_GameStates/JewelJam5/gameobjects/JewelCart.js
new file mode 100644
index 0000000..693596e
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/JewelCart.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function JewelCart(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+ this.push = 50;
+ this.minxpos = 0;
+ this.velocity = new Vector2(10, 0);
+}
+
+JewelCart.prototype = Object.create(SpriteGameObject.prototype);
+
+JewelCart.prototype.update = function (delta) {
+ SpriteGameObject.prototype.update.call(this, delta);
+ this.push = Math.max(this.push - 0.00001, 1);
+};
+
+JewelCart.prototype.reset = function () {
+ SpriteGameObject.prototype.reset.call(this);
+ this.position.x = this.minxpos;
+};
+
+JewelCart.prototype.pushCart = function () {
+ this.position.x = Math.max(this.position.x - this.push, this.minxpos);
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gameobjects/JewelGrid.js b/16_GameStates/JewelJam5/gameobjects/JewelGrid.js
new file mode 100644
index 0000000..930c117
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/JewelGrid.js
@@ -0,0 +1,174 @@
+"use strict";
+
+function JewelGrid(rows, columns, layer, id) {
+ GameObjectGrid.call(this, rows, columns, layer, id);
+ this.dragging = false;
+ this._dragRow = 0;
+ this._draggingLastX = 0;
+ this._touchIndex = 0;
+}
+
+JewelGrid.prototype = Object.create(GameObjectGrid.prototype);
+
+JewelGrid.prototype.handleInput = function (delta) {
+ if (Touch.isTouchDevice)
+ this.handleInputTouch(delta);
+ else
+ this.handleInputMouse(delta);
+};
+
+JewelGrid.prototype.handleInputMouse = function (delta) {
+ if (Mouse.left.down && !this.dragging) {
+ var rect = new Rectangle(this.worldPosition.x, this.worldPosition.y, this.columns * this.cellHeight, this.rows * this.cellWidth);
+ if (Mouse.containsMouseDown(rect)) {
+ this.dragging = true;
+ this._dragRow = Math.floor((Mouse.position.y - this.worldPosition.y) / this.cellHeight);
+ this._draggingLastX = Mouse.position.x - this.worldPosition.x;
+ }
+ }
+ if (!Mouse.left.down) {
+ this.dragging = false;
+ }
+
+ if (this.dragging) {
+ var newpos = Mouse.position.x - this.worldPosition.x;
+
+ // reposition each jewel in the row
+ for (var i = 0; i < this.columns; i++) {
+ var currObj = this.at(i, this._dragRow);
+ currObj.position.x += (newpos - this._draggingLastX);
+ }
+ // check if we need to shift a row
+ var firstObj = this.at(0, this._dragRow);
+ if (firstObj.position.x < -this.cellWidth / 2 && newpos - this._draggingLastX < 0)
+ this.shiftRowLeft(this._dragRow);
+ var lastObj = this.at(this.columns - 1, this._dragRow);
+ if (lastObj.position.x > (this.columns - 1) * this.cellWidth + this.cellWidth / 2 && newpos - this._draggingLastX > 0)
+ this.shiftRowRight(this._dragRow);
+ this._draggingLastX = newpos;
+ }
+};
+
+JewelGrid.prototype.handleInputTouch = function (delta) {
+ var pos, newpos;
+ var rect = new Rectangle(this.worldPosition.x, this.worldPosition.y, this.columns * this.cellHeight, this.rows * this.cellWidth);
+
+ if (Touch.isTouching && !this.dragging) {
+ if (Touch.containsTouch(rect)) {
+ this._touchIndex = Touch.getIndexInRect(rect);
+ pos = Touch.getPosition(this._touchIndex);
+ this.dragging = true;
+ this._dragRow = Math.floor((pos.y - this.worldPosition.y) / this.cellHeight);
+ this._draggingLastX = pos.x - this.worldPosition.x;
+ }
+ }
+ if (!Touch.isTouching) {
+ this.dragging = false;
+ }
+
+ if (this.dragging) {
+ pos = Touch.getPosition(this._touchIndex);
+ newpos = pos.x - this.worldPosition.x;
+
+ // reposition each jewel in the row
+ for (var i = 0; i < this.columns; i++) {
+ var currObj = this.at(i, this._dragRow);
+ currObj.position.x += (newpos - this._draggingLastX);
+ }
+ // check if we need to shift a row
+ var firstObj = this.at(0, this._dragRow);
+ if (firstObj.position.x < -this.cellWidth / 2 && newpos - this._draggingLastX < 0)
+ this.shiftRowLeft(this._dragRow);
+ var lastObj = this.at(this.columns - 1, this._dragRow);
+ if (lastObj.position.x > (this.columns - 1) * this.cellWidth + this.cellWidth / 2 && newpos - this._draggingLastX > 0)
+ this.shiftRowRight(this._dragRow);
+ this._draggingLastX = newpos;
+ }
+};
+
+JewelGrid.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ if (this.dragging)
+ return;
+
+ var middleCol = Math.floor(this._columns / 2);
+ var i = 0;
+ var score = this.root.find(ID.score);
+ while (i < this._rows - 2) {
+ if (this.isValidCombination(this.at(middleCol, i),
+ this.at(middleCol, i + 1),
+ this.at(middleCol, i + 2))) {
+ this.removeJewel(middleCol, i, -this.cellHeight);
+ this.removeJewel(middleCol, i + 1, -this.cellHeight * 2);
+ this.removeJewel(middleCol, i + 2, -this.cellHeight * 3);
+ score.score += 10;
+
+ var jewelCart = this.root.find(ID.jewel_cart);
+ jewelCart.pushCart();
+
+ i = 0;
+ }
+ else
+ i++;
+ }
+};
+
+JewelGrid.prototype.isValidCombination = function (a, b, c) {
+ var curra = a.variation;
+ var currb = b.variation;
+ var currc = c.variation;
+ var divider = 9;
+
+ for (var i = 0; i < 3; i += 1) {
+ if ((Math.floor(curra / divider) + Math.floor(currb / divider) + Math.floor(currc / divider)) % 3 !== 0)
+ return false;
+ curra = curra % divider;
+ currb = currb % divider;
+ currc = currc % divider;
+ divider = Math.floor(divider / 3);
+ }
+ return true;
+};
+
+JewelGrid.prototype.reset = function () {
+ this.clear();
+ for (var i = 0; i < this._rows * this._columns; i += 1)
+ this.add(new Jewel(ID.layer_objects));
+};
+
+JewelGrid.prototype.removeJewel = function (x, y, newYPosition) {
+ for (var row = y; row > 0; row -= 1)
+ this._gameObjects[row * this._columns + x] = this._gameObjects[(row - 1) * this._columns + x];
+ var jewel = new Jewel();
+ this.addAt(jewel, x, 0);
+ jewel.position.y = newYPosition;
+};
+
+JewelGrid.prototype.shiftRowRight = function (selectedRow) {
+ var lastObj = this.at(this._columns - 1, selectedRow);
+ var positionOffset = lastObj.position.x - (this.columns - 1) * this.cellWidth;
+ for (var x = this._columns - 1; x > 0; x -= 1)
+ this._gameObjects[selectedRow * this._columns + x] = this._gameObjects[selectedRow * this._columns + (x - 1)];
+ this._gameObjects[selectedRow * this._columns] = lastObj;
+ lastObj.position = new Vector2(-this.cellWidth + positionOffset, selectedRow * this.cellHeight);
+};
+
+JewelGrid.prototype.shiftRowLeft = function (selectedRow) {
+ var firstObj = this.at(0, selectedRow);
+ var positionOffset = firstObj.position.x;
+ for (var x = 0; x < this._columns - 1; x += 1)
+ this._gameObjects[selectedRow * this._columns + x] = this._gameObjects[selectedRow * this._columns + x + 1];
+ this._gameObjects[selectedRow * this._columns + (this._columns - 1)] = firstObj;
+ firstObj.position = new Vector2(this._columns * this.cellWidth + positionOffset, selectedRow * this.cellHeight);
+};
+
+JewelGrid.prototype.getAnchorPosition = function (gameobject) {
+ var l = this._gameObjects.length;
+ for (var i = 0; i < l; ++i)
+ if (this._gameObjects[i] == gameobject) {
+ var row = Math.floor(i / this.columns);
+ var col = i - row * this.columns;
+ return new Vector2(col * this.cellWidth, row * this.cellHeight);
+ }
+ return Vector2.zero;
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gameobjects/JewelJamGameWorld.js b/16_GameStates/JewelJam5/gameobjects/JewelJamGameWorld.js
new file mode 100644
index 0000000..62659a5
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/JewelJamGameWorld.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function JewelJamGameWorld(layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ // the title screen
+ var titleScreen = new SpriteGameObject(sprites.title, ID.layer_overlays_2, ID.title);
+ this.add(titleScreen);
+
+ // add a background sprite
+ var background = new SpriteGameObject(sprites.background, ID.layer_background);
+ this.add(background);
+
+ // add a score frame
+ var scoreFrame = new SpriteGameObject(sprites.frame_score, ID.layer_overlays);
+ scoreFrame.position = new Vector2(20, 20);
+ this.add(scoreFrame);
+
+ var score = new ScoreGameObject("Segoe UI Mono", "40px", ID.layer_overlays_1, ID.score);
+ score.position = new Vector2(270, 35);
+ score.color = Color.white;
+
+ this.add(score);
+
+ this.helpButton = new Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new Vector2(1268, 20);
+ this.add(this.helpButton);
+
+ var helpFrame = new SpriteGameObject(sprites.frame_help, ID.layer_overlays, ID.help_frame);
+ helpFrame.position = new Vector2(636, 120);
+ helpFrame.visible = false;
+ this.add(helpFrame);
+
+ var jewelCart = new JewelCart(sprites.jewelcart, ID.layer_objects, ID.jewel_cart);
+ jewelCart.position = new Vector2(410, 230);
+ jewelCart.minxpos = 410;
+ this.add(jewelCart);
+
+ // add the grid
+ var rows = 10, columns = 5;
+ var grid = new JewelGrid(rows, columns, ID.layer_objects, ID.grid);
+ grid.position = new Vector2(85, 150);
+ grid.cellWidth = 85;
+ grid.cellHeight = 85;
+ grid.reset();
+ this.add(grid);
+
+ // game over overlay
+ var gameOver = new SpriteGameObject(sprites.gameover, ID.layer_overlays_1, ID.game_over);
+ gameOver.visible = false;
+ gameOver.position = gameOver.screenCenter;
+ this.add(gameOver);
+}
+
+JewelJamGameWorld.prototype = Object.create(GameObjectList.prototype);
+
+JewelJamGameWorld.prototype.handleInput = function (delta) {
+ // title screen
+ var titleScreen = this.root.find(ID.title);
+ if (titleScreen.visible) {
+ if (Mouse.left.pressed || Touch.isPressing)
+ titleScreen.visible = false;
+ return;
+ }
+
+ // game over
+ if (this.gameOver()) {
+ if (Mouse.left.pressed || Touch.isPressing)
+ this.reset();
+ return;
+ }
+
+ // playing
+ GameObjectList.prototype.handleInput.call(this, delta);
+ var helpFrame = this.root.find(ID.help_frame);
+ if (this.helpButton.pressed || (helpFrame.visible && (Mouse.left.pressed || Touch.isPressing))) {
+ helpFrame.visible = !helpFrame.visible;
+ }
+};
+
+JewelJamGameWorld.prototype.update = function (delta) {
+ // title screen
+ var titleScreen = this.root.find(ID.title);
+ if (titleScreen.visible)
+ return;
+
+ // game over
+ var gameOver = this.root.find(ID.game_over);
+ if (this.gameOver()) {
+ gameOver.visible = true;
+ }
+
+ // playing
+ var helpFrame = this.root.find(ID.help_frame);
+ var grid = this.root.find(ID.grid);
+ grid.visible = !helpFrame.visible;
+ if (!helpFrame.visible)
+ GameObjectList.prototype.update.call(this, delta);
+};
+
+JewelJamGameWorld.prototype.gameOver = function () {
+ var jewelCart = this.root.find(ID.jewel_cart);
+ return jewelCart.position.x > Game.size.x;
+};
+
+JewelJamGameWorld.prototype.reset = function () {
+ GameObjectList.prototype.reset.call(this);
+ var gameOver = this.root.find(ID.game_over);
+ gameOver.visible = false;
+ var titleScreen = this.root.find(ID.title);
+ titleScreen.visible = false;
+ var helpFrame = this.root.find(ID.help_frame);
+ helpFrame.visible = false;
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gameobjects/ScoreGameObject.js b/16_GameStates/JewelJam5/gameobjects/ScoreGameObject.js
new file mode 100644
index 0000000..a543568
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/ScoreGameObject.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function ScoreGameObject(fontName, fontSize, layer, id) {
+ Label.call(this, fontName, fontSize, layer, id);
+ this.text = 0;
+ this._align = "right";
+}
+
+ScoreGameObject.prototype = Object.create(Label.prototype);
+
+Object.defineProperty(ScoreGameObject.prototype, "score",
+ {
+ get: function () {
+ return this._contents;
+ },
+ set: function (value) {
+ if (value >= 0)
+ this.text = value;
+ }
+ });
+
+ScoreGameObject.prototype.reset = function () {
+ this.text = 0;
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gameobjects/SpriteGameObject.js b/16_GameStates/JewelJam5/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..f4971d2
--- /dev/null
+++ b/16_GameStates/JewelJam5/gameobjects/SpriteGameObject.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer, id) {
+ GameObject.call(this, layer, id);
+ this.sprite = sprite;
+ this.origin = Vector2.zero;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "boundingBox",
+ {
+ get: function () {
+ var leftTop = this.worldPosition.subtractFrom((this.origin));
+ return new Rectangle(leftTop.x, leftTop.y, this.width, this.height);
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawImage(this.sprite, this.worldPosition, 0, 1, this.origin);
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/geom/Rectangle.js b/16_GameStates/JewelJam5/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/16_GameStates/JewelJam5/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/geom/Vector2.js b/16_GameStates/JewelJam5/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/16_GameStates/JewelJam5/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gui/Button.js b/16_GameStates/JewelJam5/gui/Button.js
new file mode 100644
index 0000000..9182296
--- /dev/null
+++ b/16_GameStates/JewelJam5/gui/Button.js
@@ -0,0 +1,18 @@
+"use strict";
+
+function Button(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+
+ this.pressed = false;
+ this.down = false;
+}
+
+Button.prototype = Object.create(SpriteGameObject.prototype);
+
+Button.prototype.handleInput = function (delta) {
+ var boundingBox = this.boundingBox;
+ this.pressed = this.visible && (Touch.containsTouchPress(boundingBox) ||
+ Mouse.containsMousePress(boundingBox));
+ this.down = this.visible && (Touch.containsTouch(boundingBox) ||
+ Mouse.containsMouseDown(boundingBox));
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/gui/Label.js b/16_GameStates/JewelJam5/gui/Label.js
new file mode 100644
index 0000000..41c5af6
--- /dev/null
+++ b/16_GameStates/JewelJam5/gui/Label.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function calculateTextSize(fontname, fontsize, text) {
+ var div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = -1000;
+ div.style.top = -1000;
+ document.body.appendChild(div);
+ text = typeof text !== 'undefined' ? text : "M";
+ div.style.fontSize = "" + fontsize;
+ div.style.fontFamily = fontname;
+ div.innerHTML = text;
+ var size = new Vector2(div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+ return size;
+}
+
+function Label(fontname, fontsize, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.color = Color.black;
+ this.origin = Vector2.zero;
+ this._fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ this._fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+ this._contents = "";
+ this._align = "left";
+ this._size = Vector2.zero;
+}
+
+Label.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(Label.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "width",
+ {
+ get: function () {
+ return this._size.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "height",
+ {
+ get: function () {
+ return this._size.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(Label.prototype, "text",
+ {
+ get: function () {
+ return this._contents;
+ },
+
+ set: function (value) {
+ this._contents = value;
+ this._size = calculateTextSize(this._fontname, this._fontsize, value);
+ }
+ });
+
+Label.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawText(this._contents, this.worldPosition,
+ this.origin, this.color, this._align,
+ this._fontname, this._fontsize);
+};
diff --git a/16_GameStates/JewelJam5/input/ButtonState.js b/16_GameStates/JewelJam5/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/16_GameStates/JewelJam5/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/input/Keyboard.js b/16_GameStates/JewelJam5/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/16_GameStates/JewelJam5/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/16_GameStates/JewelJam5/input/Mouse.js b/16_GameStates/JewelJam5/input/Mouse.js
new file mode 100644
index 0000000..22bc378
--- /dev/null
+++ b/16_GameStates/JewelJam5/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/16_GameStates/JewelJam5/input/Touch.js b/16_GameStates/JewelJam5/input/Touch.js
new file mode 100644
index 0000000..fea1e09
--- /dev/null
+++ b/16_GameStates/JewelJam5/input/Touch.js
@@ -0,0 +1,124 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isPressing",
+ {
+ get: function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ if (this._touchPresses[i]) {
+ return true;
+ }
+ return false;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.isValid = function (index) {
+ return index >= 0 && index < this._touches.length;
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/system/Color.js b/16_GameStates/JewelJam5/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/16_GameStates/JewelJam5/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/16_GameStates/JewelJam5/system/Keys.js b/16_GameStates/JewelJam5/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/16_GameStates/JewelJam5/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/16_GameStates/LAB.min.js b/16_GameStates/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/16_GameStates/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/Canvas2D.js b/17_FinishingGame/JewelJamFinal/Canvas2D.js
new file mode 100644
index 0000000..f9ed84b
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/Canvas2D.js
@@ -0,0 +1,118 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+ window.onresize = Canvas2D_Singleton.prototype.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x * scale, -origin.y * scale,
+ sourceRect.width * scale, sourceRect.height * scale);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
+
diff --git a/17_FinishingGame/JewelJamFinal/Game.js b/17_FinishingGame/JewelJamFinal/Game.js
new file mode 100644
index 0000000..bab8420
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/Game.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ this.gameWorld = null;
+
+ // for debugging
+ this.log = "";
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ this._totalSprites += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ // displaying the number of touches
+ Canvas2D.drawText(Game.log);
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/JewelJam.html b/17_FinishingGame/JewelJamFinal/JewelJam.html
new file mode 100644
index 0000000..2e22c19
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/JewelJam.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+ Jewel Jam
+
+
+
+ Jewel Jam
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/JewelJam.js b/17_FinishingGame/JewelJamFinal/JewelJam.js
new file mode 100644
index 0000000..1ee9de6
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/JewelJam.js
@@ -0,0 +1,84 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return Game.loadSprite("../../assets/JewelJam/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/JewelJam/sounds/" + sound, looping);
+ };
+
+ sprites.background = loadSprite("spr_background.jpg");
+ sprites.frame_score = loadSprite("spr_frame_score.jpg");
+ sprites.button_help = loadSprite("spr_button_help.jpg");
+ sprites.frame_help = loadSprite("spr_frame_help.png");
+ sprites.jewelcart = loadSprite("spr_jewelcart.png");
+ sprites.jewels = loadSprite("spr_jewels@27.png");
+ if (Touch.isTouchDevice) {
+ sprites.gameover = loadSprite("spr_gameover_tap.png");
+ sprites.title = loadSprite("spr_title_tap.jpg");
+ } else {
+ sprites.gameover = loadSprite("spr_gameover_click.png");
+ sprites.title = loadSprite("spr_title_click.jpg");
+ }
+ sprites.frame_selector = loadSprite("spr_frame_selector.png");
+ sprites.glitter = loadSprite("spr_glitter.png");
+ sprites.double = loadSprite("spr_double.png");
+ sprites.triple = loadSprite("spr_triple.png");
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.combi = loadSound("snd_combi");
+ sounds.double = loadSound("snd_double");
+ sounds.triple = loadSound("snd_triple");
+ sounds.gameover = loadSound("snd_gameover");
+};
+
+Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.title = 1;
+ ID.help_frame = 2;
+ ID.jewel_cart = 3;
+ ID.grid = 4;
+ ID.game_over = 5;
+ ID.score = 6;
+ ID.double_timer = 7;
+ ID.triple_timer = 8;
+
+ // create the game world
+ Game.gameWorld = new JewelJamGameWorld();
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/17_FinishingGame/JewelJamFinal/Math.js b/17_FinishingGame/JewelJamFinal/Math.js
new file mode 100644
index 0000000..5e1da3b
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/Math.js
@@ -0,0 +1,23 @@
+"use strict";
+
+if (!Math.sign) {
+ Math.sign = function (value) {
+ if (value > 0)
+ return 1;
+ else if (value < 0)
+ return -1;
+ else
+ return 0;
+ };
+}
+
+if (!Math.clamp) {
+ Math.clamp = function (value, min, max) {
+ if (value < min)
+ return min;
+ else if (value > max)
+ return max;
+ else
+ return value;
+ };
+}
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/Sound.js b/17_FinishingGame/JewelJamFinal/Sound.js
new file mode 100644
index 0000000..e4b7afe
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function(value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function() {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/game-layout.css b/17_FinishingGame/JewelJamFinal/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/GameObject.js b/17_FinishingGame/JewelJamFinal/gameobjects/GameObject.js
new file mode 100644
index 0000000..09a33ff
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/GameObject.js
@@ -0,0 +1,58 @@
+"use strict";
+
+function GameObject(layer, id) {
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
+
+GameObject.prototype.handleInput = function (delta) {
+};
+
+GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+GameObject.prototype.draw = function () {
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/GameObjectGrid.js b/17_FinishingGame/JewelJamFinal/gameobjects/GameObjectGrid.js
new file mode 100644
index 0000000..e79c4ed
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/GameObjectGrid.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function GameObjectGrid(rows, columns, layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.cellWidth = 0;
+ this.cellHeight = 0;
+ this._rows = rows;
+ this._columns = columns;
+}
+
+GameObjectGrid.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(GameObjectGrid.prototype, "rows", {
+ get: function () {
+ return this._rows;
+ }
+});
+
+Object.defineProperty(GameObjectGrid.prototype, "columns", {
+ get: function () {
+ return this._columns;
+ }
+});
+
+GameObjectGrid.prototype.add = function (gameobject) {
+ var row = Math.floor(this._gameObjects.length / this._columns);
+ var col = this._gameObjects.length % this._columns;
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.addAt = function (gameobject, col, row) {
+ this._gameObjects[row * this._columns + col] = gameobject;
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.at = function (col, row) {
+ var index = row * this._columns + col;
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ else
+ return this._gameObjects[index];
+};
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/GameObjectList.js b/17_FinishingGame/JewelJamFinal/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..c595ced
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/GameObjectList.js
@@ -0,0 +1,81 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer, id) {
+ GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/GlitterField.js b/17_FinishingGame/JewelJamFinal/gameobjects/GlitterField.js
new file mode 100644
index 0000000..8e3edd2
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/GlitterField.js
@@ -0,0 +1,50 @@
+"use strict";
+
+function GlitterField(density, width, height, layer, id) {
+ GameObject.call(this, layer, id);
+ this.positions = [];
+ this.scales = [];
+ this._scaleMax = 1;
+ this._scaleStep = 0.05;
+ this.width = width;
+ this.height = height;
+
+ // fill it with random positions
+ for (var i = 0; i < density; i++) {
+ this.positions.push(this.createRandomPosition());
+ this.scales.push(0);
+ }
+}
+
+GlitterField.prototype = Object.create(GameObject.prototype);
+
+GlitterField.prototype.createRandomPosition = function () {
+ return new Vector2(Math.random() * this.width, Math.random() * this.height);
+};
+
+GlitterField.prototype.update = function (delta) {
+ for (var i = 0; i < this.scales.length; i += 1) {
+ if (this.scales[i] === 0 && Math.random() < 0.01)
+ this.scales[i] += this._scaleStep;
+ else if (this.scales[i] !== 0) {
+ this.scales[i] += this._scaleStep;
+ if (this.scales[i] >= this._scaleMax * 2) {
+ this.scales[i] = 0;
+ this.positions[i] = this.createRandomPosition();
+ }
+ }
+ }
+};
+
+GlitterField.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ var origin = new Vector2(sprites.glitter.width / 2, sprites.glitter.height / 2);
+ for (var i = 0; i < this.scales.length; i++) {
+ var scale = this.scales[i];
+ if (this.scales[i] > this._scaleMax)
+ scale = this._scaleMax * 2 - this.scales[i];
+ var pos = this.worldPosition.addTo(this.positions[i]);
+ Canvas2D.drawImage(sprites.glitter, pos, 0, scale, origin);
+ }
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/Jewel.js b/17_FinishingGame/JewelJamFinal/gameobjects/Jewel.js
new file mode 100644
index 0000000..40e5a88
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/Jewel.js
@@ -0,0 +1,25 @@
+"use strict";
+
+function Jewel(layer, id) {
+ SpriteGameObject.call(this, sprites.jewels, layer, id);
+ this.variation = Math.floor(Math.random() * 27);
+}
+
+Jewel.prototype = Object.create(SpriteGameObject.prototype);
+
+Jewel.prototype.update = function (delta) {
+ SpriteGameObject.prototype.update.call(this, delta);
+
+ if (this.parent.dragging)
+ return;
+ var anchor = this.parent.getAnchorPosition(this);
+ this.velocity = anchor.subtractFrom(this.position).multiplyWith(15);
+};
+
+Jewel.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ var imagePart = new Rectangle(this.variation * this.height, 0,
+ this.height, this.height);
+ Canvas2D.drawImage(this.sprite, this.worldPosition, 0, 1, this.origin, imagePart);
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/JewelCart.js b/17_FinishingGame/JewelJamFinal/gameobjects/JewelCart.js
new file mode 100644
index 0000000..f99a2f8
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/JewelCart.js
@@ -0,0 +1,33 @@
+"use strict";
+
+function JewelCart(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+ this.push = 50;
+ this.minxpos = 0;
+ this.velocity = new Vector2(10, 0);
+ this.glitters = new GlitterField(2, 435, 75);
+ this.glitters.position = new Vector2(275, 475);
+ this.glitters.parent = this;
+}
+
+JewelCart.prototype = Object.create(SpriteGameObject.prototype);
+
+JewelCart.prototype.update = function (delta) {
+ SpriteGameObject.prototype.update.call(this, delta);
+ this.push = Math.max(this.push - 0.00001, 1);
+ this.glitters.update(delta);
+};
+
+JewelCart.prototype.reset = function () {
+ SpriteGameObject.prototype.reset.call(this);
+ this.position.x = this.minxpos;
+};
+
+JewelCart.prototype.draw = function () {
+ SpriteGameObject.prototype.draw.call(this);
+ this.glitters.draw();
+};
+
+JewelCart.prototype.pushCart = function () {
+ this.position.x = Math.max(this.position.x - this.push, this.minxpos);
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/JewelGrid.js b/17_FinishingGame/JewelJamFinal/gameobjects/JewelGrid.js
new file mode 100644
index 0000000..d7b9ef4
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/JewelGrid.js
@@ -0,0 +1,191 @@
+"use strict";
+
+function JewelGrid(rows, columns, layer, id) {
+ GameObjectGrid.call(this, rows, columns, layer, id);
+ this.dragging = false;
+ this._dragRow = 0;
+ this._draggingLastX = 0;
+ this._touchIndex = 0;
+}
+
+JewelGrid.prototype = Object.create(GameObjectGrid.prototype);
+
+JewelGrid.prototype.handleInput = function (delta) {
+ if (Touch.isTouchDevice)
+ this.handleInputTouch(delta);
+ else
+ this.handleInputMouse(delta);
+};
+
+JewelGrid.prototype.handleInputMouse = function (delta) {
+ if (Mouse.left.down && !this.dragging) {
+ var rect = new Rectangle(this.worldPosition.x, this.worldPosition.y, this.columns * this.cellHeight, this.rows * this.cellWidth);
+ if (Mouse.containsMouseDown(rect)) {
+ this.dragging = true;
+ this._dragRow = Math.floor((Mouse.position.y - this.worldPosition.y) / this.cellHeight);
+ this._draggingLastX = Mouse.position.x - this.worldPosition.x;
+ }
+ }
+ if (!Mouse.left.down) {
+ this.dragging = false;
+ }
+
+ if (this.dragging) {
+ var newpos = Mouse.position.x - this.worldPosition.x;
+
+ // reposition each jewel in the row
+ for (var i = 0; i < this.columns; i++) {
+ var currObj = this.at(i, this._dragRow);
+ currObj.position.x += (newpos - this._draggingLastX);
+ }
+ // check if we need to shift a row
+ var firstObj = this.at(0, this._dragRow);
+ if (firstObj.position.x < -this.cellWidth / 2 && newpos - this._draggingLastX < 0)
+ this.shiftRowLeft(this._dragRow);
+ var lastObj = this.at(this.columns - 1, this._dragRow);
+ if (lastObj.position.x > (this.columns - 1) * this.cellWidth + this.cellWidth / 2 && newpos - this._draggingLastX > 0)
+ this.shiftRowRight(this._dragRow);
+ this._draggingLastX = newpos;
+ }
+};
+
+JewelGrid.prototype.handleInputTouch = function (delta) {
+ var pos, newpos;
+ var rect = new Rectangle(this.worldPosition.x, this.worldPosition.y, this.columns * this.cellHeight, this.rows * this.cellWidth);
+
+ if (Touch.isTouching && !this.dragging) {
+ if (Touch.containsTouch(rect)) {
+ this._touchIndex = Touch.getIndexInRect(rect);
+ pos = Touch.getPosition(this._touchIndex);
+ this.dragging = true;
+ this._dragRow = Math.floor((pos.y - this.worldPosition.y) / this.cellHeight);
+ this._draggingLastX = pos.x - this.worldPosition.x;
+ }
+ }
+ if (!Touch.isTouching) {
+ this.dragging = false;
+ }
+
+ if (this.dragging) {
+ pos = Touch.getPosition(this._touchIndex);
+ newpos = pos.x - this.worldPosition.x;
+
+ // reposition each jewel in the row
+ for (var i = 0; i < this.columns; i++) {
+ var currObj = this.at(i, this._dragRow);
+ currObj.position.x += (newpos - this._draggingLastX);
+ }
+ // check if we need to shift a row
+ var firstObj = this.at(0, this._dragRow);
+ if (firstObj.position.x < -this.cellWidth / 2 && newpos - this._draggingLastX < 0)
+ this.shiftRowLeft(this._dragRow);
+ var lastObj = this.at(this.columns - 1, this._dragRow);
+ if (lastObj.position.x > (this.columns - 1) * this.cellWidth + this.cellWidth / 2 && newpos - this._draggingLastX > 0)
+ this.shiftRowRight(this._dragRow);
+ this._draggingLastX = newpos;
+ }
+};
+
+JewelGrid.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ if (this.dragging)
+ return;
+
+ var middleCol = Math.floor(this._columns / 2);
+ var nrCombis = 0;
+ var i = 0;
+ var score = this.root.find(ID.score);
+ while (i < this._rows - 2) {
+ if (this.isValidCombination(this.at(middleCol, i),
+ this.at(middleCol, i + 1),
+ this.at(middleCol, i + 2))) {
+ this.removeJewel(middleCol, i, -this.cellHeight);
+ this.removeJewel(middleCol, i + 1, -this.cellHeight * 2);
+ this.removeJewel(middleCol, i + 2, -this.cellHeight * 3);
+ score.score += 10;
+
+ var jewelCart = this.root.find(ID.jewel_cart);
+ jewelCart.pushCart();
+
+ nrCombis++;
+ i = 0;
+ }
+ else
+ i++;
+ }
+ if (nrCombis === 1) {
+ sounds.combi.play();
+ }
+ else if (nrCombis === 2) {
+ score.score += 50;
+ var doubleTimer = this.root.find(ID.double_timer);
+ doubleTimer.startVisible();
+ sounds.double.play();
+ }
+ else if (nrCombis >= 3) {
+ score.score += 100;
+ var tripleTimer = this.root.find(ID.triple_timer);
+ tripleTimer.startVisible();
+ sounds.triple.play();
+ }
+};
+
+JewelGrid.prototype.isValidCombination = function (a, b, c) {
+ var curra = a.variation;
+ var currb = b.variation;
+ var currc = c.variation;
+ var divider = 9;
+
+ for (var i = 0; i < 3; i += 1) {
+ if ((Math.floor(curra / divider) + Math.floor(currb / divider) + Math.floor(currc / divider)) % 3 !== 0)
+ return false;
+ curra = curra % divider;
+ currb = currb % divider;
+ currc = currc % divider;
+ divider = Math.floor(divider / 3);
+ }
+ return true;
+};
+
+JewelGrid.prototype.reset = function () {
+ this.clear();
+ for (var i = 0; i < this._rows * this._columns; i += 1)
+ this.add(new Jewel(ID.layer_objects));
+};
+
+JewelGrid.prototype.removeJewel = function (x, y, newYPosition) {
+ for (var row = y; row > 0; row -= 1)
+ this._gameObjects[row * this._columns + x] = this._gameObjects[(row - 1) * this._columns + x];
+ var jewel = new Jewel();
+ this.addAt(jewel, x, 0);
+ jewel.position.y = newYPosition;
+};
+
+JewelGrid.prototype.shiftRowRight = function (selectedRow) {
+ var lastObj = this.at(this._columns - 1, selectedRow);
+ var positionOffset = lastObj.position.x - (this.columns - 1) * this.cellWidth;
+ for (var x = this._columns - 1; x > 0; x -= 1)
+ this._gameObjects[selectedRow * this._columns + x] = this._gameObjects[selectedRow * this._columns + (x - 1)];
+ this._gameObjects[selectedRow * this._columns] = lastObj;
+ lastObj.position = new Vector2(-this.cellWidth + positionOffset, selectedRow * this.cellHeight);
+};
+
+JewelGrid.prototype.shiftRowLeft = function (selectedRow) {
+ var firstObj = this.at(0, selectedRow);
+ var positionOffset = firstObj.position.x;
+ for (var x = 0; x < this._columns - 1; x += 1)
+ this._gameObjects[selectedRow * this._columns + x] = this._gameObjects[selectedRow * this._columns + x + 1];
+ this._gameObjects[selectedRow * this._columns + (this._columns - 1)] = firstObj;
+ firstObj.position = new Vector2(this._columns * this.cellWidth + positionOffset, selectedRow * this.cellHeight);
+};
+
+JewelGrid.prototype.getAnchorPosition = function (gameobject) {
+ var l = this._gameObjects.length;
+ for (var i = 0; i < l; ++i)
+ if (this._gameObjects[i] == gameobject) {
+ var row = Math.floor(i / this.columns);
+ var col = i - row * this.columns;
+ return new Vector2(col * this.cellWidth, row * this.cellHeight);
+ }
+ return Vector2.zero;
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/JewelJamGameWorld.js b/17_FinishingGame/JewelJamFinal/gameobjects/JewelJamGameWorld.js
new file mode 100644
index 0000000..4e85376
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/JewelJamGameWorld.js
@@ -0,0 +1,136 @@
+"use strict";
+
+function JewelJamGameWorld(layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ // the title screen
+ var titleScreen = new SpriteGameObject(sprites.title, ID.layer_overlays_2, ID.title);
+ this.add(titleScreen);
+
+ // add a background sprite
+ var background = new SpriteGameObject(sprites.background, ID.layer_background);
+ this.add(background);
+
+ // add a score frame
+ var scoreFrame = new SpriteGameObject(sprites.frame_score, ID.layer_overlays);
+ scoreFrame.position = new Vector2(20, 20);
+ this.add(scoreFrame);
+
+ var score = new ScoreGameObject("Segoe UI Mono", "40px", ID.layer_overlays_1, ID.score);
+ score.position = new Vector2(270, 35);
+ score.color = Color.white;
+
+ this.add(score);
+
+ this.helpButton = new Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new Vector2(1268, 20);
+ this.add(this.helpButton);
+
+ var helpFrame = new SpriteGameObject(sprites.frame_help, ID.layer_overlays, ID.help_frame);
+ helpFrame.position = new Vector2(636, 120);
+ helpFrame.visible = false;
+ this.add(helpFrame);
+
+ var jewelCart = new JewelCart(sprites.jewelcart, ID.layer_objects, ID.jewel_cart);
+ jewelCart.position = new Vector2(410, 230);
+ jewelCart.minxpos = 410;
+ this.add(jewelCart);
+
+ // add the playing field
+ var playingField = new GameObjectList(ID.layer_objects);
+ playingField.position = new Vector2(85, 150);
+ this.add(playingField);
+
+ // add the grid
+ var rows = 10, columns = 5;
+ var grid = new JewelGrid(rows, columns, ID.layer_objects, ID.grid);
+ grid.cellWidth = 85;
+ grid.cellHeight = 85;
+ grid.reset();
+ playingField.add(grid);
+
+ // add glitters
+ playingField.add(new GlitterField(2, columns * grid.cellWidth, rows * grid.cellHeight, ID.layer_overlays_1));
+
+ // game over overlay
+ var gameOver = new SpriteGameObject(sprites.gameover, ID.layer_overlays_1, ID.game_over);
+ gameOver.visible = false;
+ gameOver.position = gameOver.screenCenter;
+ this.add(gameOver);
+
+ // double/triple overlays
+ var doubleOverlay = new SpriteGameObject(sprites.double, ID.layer_overlays);
+ doubleOverlay.position = new Vector2(800, 400);
+ var doubleTimer = new VisibilityTimer(doubleOverlay, ID.layer_overlays, ID.double_timer);
+ this.add(doubleOverlay);
+ this.add(doubleTimer);
+
+ var tripleOverlay = new SpriteGameObject(sprites.triple, ID.layer_overlays);
+ tripleOverlay.position = new Vector2(800, 400);
+ var tripleTimer = new VisibilityTimer(tripleOverlay, ID.layer_overlays, ID.triple_timer);
+ this.add(tripleOverlay);
+ this.add(tripleTimer);
+}
+
+JewelJamGameWorld.prototype = Object.create(GameObjectList.prototype);
+
+JewelJamGameWorld.prototype.handleInput = function (delta) {
+ // title screen
+ var titleScreen = this.root.find(ID.title);
+ if (titleScreen.visible) {
+ if (Mouse.left.pressed || Touch.isPressing)
+ titleScreen.visible = false;
+ return;
+ }
+
+ // game over
+ if (this.gameOver()) {
+ if (Mouse.left.pressed || Touch.isPressing)
+ this.reset();
+ return;
+ }
+
+ // playing
+ GameObjectList.prototype.handleInput.call(this, delta);
+ var helpFrame = this.root.find(ID.help_frame);
+ if (this.helpButton.pressed || (helpFrame.visible && (Mouse.left.pressed || Touch.isPressing))) {
+ helpFrame.visible = !helpFrame.visible;
+ }
+};
+
+JewelJamGameWorld.prototype.update = function (delta) {
+ // title screen
+ var titleScreen = this.root.find(ID.title);
+ if (titleScreen.visible)
+ return;
+
+ // game over
+ var gameOver = this.root.find(ID.game_over);
+ if (this.gameOver() && !gameOver.visible) {
+ gameOver.visible = true;
+ sounds.gameover.play();
+ return;
+ }
+
+ // playing
+ var helpFrame = this.root.find(ID.help_frame);
+ var grid = this.root.find(ID.grid);
+ grid.visible = !helpFrame.visible;
+ if (!helpFrame.visible)
+ GameObjectList.prototype.update.call(this, delta);
+};
+
+JewelJamGameWorld.prototype.gameOver = function () {
+ var jewelCart = this.root.find(ID.jewel_cart);
+ return jewelCart.position.x > Game.size.x;
+};
+
+JewelJamGameWorld.prototype.reset = function () {
+ GameObjectList.prototype.reset.call(this);
+ var gameOver = this.root.find(ID.game_over);
+ gameOver.visible = false;
+ var titleScreen = this.root.find(ID.title);
+ titleScreen.visible = false;
+ var helpFrame = this.root.find(ID.help_frame);
+ helpFrame.visible = false;
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/ScoreGameObject.js b/17_FinishingGame/JewelJamFinal/gameobjects/ScoreGameObject.js
new file mode 100644
index 0000000..a543568
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/ScoreGameObject.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function ScoreGameObject(fontName, fontSize, layer, id) {
+ Label.call(this, fontName, fontSize, layer, id);
+ this.text = 0;
+ this._align = "right";
+}
+
+ScoreGameObject.prototype = Object.create(Label.prototype);
+
+Object.defineProperty(ScoreGameObject.prototype, "score",
+ {
+ get: function () {
+ return this._contents;
+ },
+ set: function (value) {
+ if (value >= 0)
+ this.text = value;
+ }
+ });
+
+ScoreGameObject.prototype.reset = function () {
+ this.text = 0;
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/SpriteGameObject.js b/17_FinishingGame/JewelJamFinal/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..f4971d2
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/SpriteGameObject.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer, id) {
+ GameObject.call(this, layer, id);
+ this.sprite = sprite;
+ this.origin = Vector2.zero;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "boundingBox",
+ {
+ get: function () {
+ var leftTop = this.worldPosition.subtractFrom((this.origin));
+ return new Rectangle(leftTop.x, leftTop.y, this.width, this.height);
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawImage(this.sprite, this.worldPosition, 0, 1, this.origin);
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gameobjects/VisibilityTimer.js b/17_FinishingGame/JewelJamFinal/gameobjects/VisibilityTimer.js
new file mode 100644
index 0000000..0c6974d
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gameobjects/VisibilityTimer.js
@@ -0,0 +1,23 @@
+"use strict";
+
+function VisibilityTimer(target, layer, id) {
+ GameObject.call(this, layer, id);
+ this._target = target;
+ this._timeLeft = 0;
+ this.totalTime = 1;
+}
+
+VisibilityTimer.prototype = Object.create(GameObject.prototype);
+
+VisibilityTimer.prototype.update = function (delta) {
+ if (this._timeLeft > 0) {
+ this._timeLeft -= delta;
+ this._target.visible = true;
+ } else
+ this._target.visible = false;
+};
+
+VisibilityTimer.prototype.startVisible = function () {
+ this._timeLeft = this.totalTime;
+ this._target.visible = true;
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/geom/Rectangle.js b/17_FinishingGame/JewelJamFinal/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/geom/Vector2.js b/17_FinishingGame/JewelJamFinal/geom/Vector2.js
new file mode 100644
index 0000000..5b19710
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor === Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor === Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor === Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor === Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor === Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gui/Button.js b/17_FinishingGame/JewelJamFinal/gui/Button.js
new file mode 100644
index 0000000..9182296
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gui/Button.js
@@ -0,0 +1,18 @@
+"use strict";
+
+function Button(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+
+ this.pressed = false;
+ this.down = false;
+}
+
+Button.prototype = Object.create(SpriteGameObject.prototype);
+
+Button.prototype.handleInput = function (delta) {
+ var boundingBox = this.boundingBox;
+ this.pressed = this.visible && (Touch.containsTouchPress(boundingBox) ||
+ Mouse.containsMousePress(boundingBox));
+ this.down = this.visible && (Touch.containsTouch(boundingBox) ||
+ Mouse.containsMouseDown(boundingBox));
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/gui/Label.js b/17_FinishingGame/JewelJamFinal/gui/Label.js
new file mode 100644
index 0000000..41c5af6
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/gui/Label.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function calculateTextSize(fontname, fontsize, text) {
+ var div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = -1000;
+ div.style.top = -1000;
+ document.body.appendChild(div);
+ text = typeof text !== 'undefined' ? text : "M";
+ div.style.fontSize = "" + fontsize;
+ div.style.fontFamily = fontname;
+ div.innerHTML = text;
+ var size = new Vector2(div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+ return size;
+}
+
+function Label(fontname, fontsize, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.color = Color.black;
+ this.origin = Vector2.zero;
+ this._fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ this._fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+ this._contents = "";
+ this._align = "left";
+ this._size = Vector2.zero;
+}
+
+Label.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(Label.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "width",
+ {
+ get: function () {
+ return this._size.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "height",
+ {
+ get: function () {
+ return this._size.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(Label.prototype, "text",
+ {
+ get: function () {
+ return this._contents;
+ },
+
+ set: function (value) {
+ this._contents = value;
+ this._size = calculateTextSize(this._fontname, this._fontsize, value);
+ }
+ });
+
+Label.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawText(this._contents, this.worldPosition,
+ this.origin, this.color, this._align,
+ this._fontname, this._fontsize);
+};
diff --git a/17_FinishingGame/JewelJamFinal/input/ButtonState.js b/17_FinishingGame/JewelJamFinal/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/input/Keyboard.js b/17_FinishingGame/JewelJamFinal/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/17_FinishingGame/JewelJamFinal/input/Mouse.js b/17_FinishingGame/JewelJamFinal/input/Mouse.js
new file mode 100644
index 0000000..22bc378
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
diff --git a/17_FinishingGame/JewelJamFinal/input/Touch.js b/17_FinishingGame/JewelJamFinal/input/Touch.js
new file mode 100644
index 0000000..fea1e09
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/input/Touch.js
@@ -0,0 +1,124 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isPressing",
+ {
+ get: function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ if (this._touchPresses[i]) {
+ return true;
+ }
+ return false;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.isValid = function (index) {
+ return index >= 0 && index < this._touches.length;
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/system/Color.js b/17_FinishingGame/JewelJamFinal/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/17_FinishingGame/JewelJamFinal/system/Keys.js b/17_FinishingGame/JewelJamFinal/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/17_FinishingGame/JewelJamFinal/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/17_FinishingGame/LAB.min.js b/17_FinishingGame/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/17_FinishingGame/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/18_SpriteSheets/LAB.min.js b/18_SpriteSheets/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/18_SpriteSheets/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/Canvas2D.js b/18_SpriteSheets/PenguinPairs1/Canvas2D.js
new file mode 100644
index 0000000..62d50c1
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/Canvas2D.js
@@ -0,0 +1,138 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._pixeldrawingCanvas = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+
+ this._pixeldrawingCanvas = document.createElement('canvas');
+
+ window.onresize = this.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ this._canvasContext.scale(scale * canvasScale.x, scale * canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawPixel = function (x, y, color) {
+ var canvasscale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasscale.x, canvasscale.y);
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.fillRect(x, y, 1, 1);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawRectangle = function (x, y, width, height) {
+ var canvasScale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.strokeRect(x, y, width, height);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
diff --git a/18_SpriteSheets/PenguinPairs1/Game.js b/18_SpriteSheets/PenguinPairs1/Game.js
new file mode 100644
index 0000000..bab8420
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/Game.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ this.gameWorld = null;
+
+ // for debugging
+ this.log = "";
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ this._totalSprites += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ // displaying the number of touches
+ Canvas2D.drawText(Game.log);
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/LAB.min.js b/18_SpriteSheets/PenguinPairs1/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/PenguinPairs.html b/18_SpriteSheets/PenguinPairs1/PenguinPairs.html
new file mode 100644
index 0000000..fa154ad
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/PenguinPairs.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+ Penguin Pairs
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/PenguinPairs.js b/18_SpriteSheets/PenguinPairs1/PenguinPairs.js
new file mode 100644
index 0000000..124ace2
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/PenguinPairs.js
@@ -0,0 +1,37 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return new SpriteSheet("../../assets/PenguinPairs/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/PenguinPairs/sounds/" + sound, looping);
+ };
+
+ sprites.background_level = loadSprite("spr_background_level.jpg");
+ sprites.penguin = loadSprite("spr_penguin@4x2.png");
+};
+
+Game.initialize = function () {
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // create the game world
+ Game.gameWorld = new PenguinPairsGameWorld();
+};
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/SpriteSheet.js b/18_SpriteSheets/PenguinPairs1/SpriteSheet.js
new file mode 100644
index 0000000..d533348
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/SpriteSheet.js
@@ -0,0 +1,77 @@
+"use strict";
+
+function SpriteSheet(imageName) {
+ console.log("Loading sprite: " + imageName);
+ Game._spritesStillLoading += 1;
+ Game._totalSprites += 1;
+
+ this._image = new Image();
+ this._image.src = imageName;
+ this._image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+
+ this._sheetColumns = 1;
+ this._sheetRows = 1;
+
+ // determine the number of sheet rows and columns
+ var pathSplit = imageName.split('/');
+ var fileName = pathSplit[pathSplit.length - 1];
+ var fileSplit = fileName.split(".")[0].split("@");
+ if (fileSplit.length <= 1)
+ return;
+ var colRow = fileSplit[fileSplit.length - 1].split("x");
+ this._sheetColumns = colRow[0];
+ if (colRow.length === 2)
+ this._sheetRows = colRow[1];
+}
+
+Object.defineProperty(SpriteSheet.prototype, "image",
+ {
+ get: function () {
+ return this._image;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "width",
+ {
+ get: function () {
+ return this._image.width / this._sheetColumns;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "height",
+ {
+ get: function () {
+ return this._image.height / this._sheetRows;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "center",
+ {
+ get: function () {
+ return this.size.divideBy(2);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "nrSheetElements",
+ {
+ get: function () {
+ return this._sheetRows * this._sheetColumns;
+ }
+ });
+
+SpriteSheet.prototype.draw = function (position, origin, sheetIndex, mirror) {
+ var columnIndex = sheetIndex % this._sheetColumns;
+ var rowIndex = Math.floor(sheetIndex / this._sheetColumns) % this._sheetRows;
+ var imagePart = new Rectangle(columnIndex * this.width, rowIndex * this.height,
+ this.width, this.height);
+ Canvas2D.drawImage(this._image, position, 0, 1, origin, imagePart, mirror);
+};
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/game-layout.css b/18_SpriteSheets/PenguinPairs1/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/gameobjects/GameObject.js b/18_SpriteSheets/PenguinPairs1/gameobjects/GameObject.js
new file mode 100644
index 0000000..09a33ff
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/gameobjects/GameObject.js
@@ -0,0 +1,58 @@
+"use strict";
+
+function GameObject(layer, id) {
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
+
+GameObject.prototype.handleInput = function (delta) {
+};
+
+GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+GameObject.prototype.draw = function () {
+};
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/gameobjects/GameObjectList.js b/18_SpriteSheets/PenguinPairs1/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..062be32
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/gameobjects/GameObjectList.js
@@ -0,0 +1,79 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer, id) {
+ GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
diff --git a/18_SpriteSheets/PenguinPairs1/gameobjects/PenguinPairsGameWorld.js b/18_SpriteSheets/PenguinPairs1/gameobjects/PenguinPairsGameWorld.js
new file mode 100644
index 0000000..3897c10
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/gameobjects/PenguinPairsGameWorld.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function PenguinPairsGameWorld(layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.add(new SpriteGameObject(sprites.background_level, ID.layer_background));
+
+ this.penguin = new SpriteGameObject(sprites.penguin, ID.layer_objects);
+ this.penguin.position = new Vector2(500, 420);
+ this.add(this.penguin);
+
+}
+
+PenguinPairsGameWorld.prototype = Object.create(GameObjectList.prototype);
+
+PenguinPairsGameWorld.prototype.handleInput = function (delta) {
+ if (Keyboard.pressed(Keys.left))
+ this.penguin.sheetIndex--;
+ else if (Keyboard.pressed(Keys.right))
+ this.penguin.sheetIndex++;
+};
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/gameobjects/SpriteGameObject.js b/18_SpriteSheets/PenguinPairs1/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..fddf0d3
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/gameobjects/SpriteGameObject.js
@@ -0,0 +1,84 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.sprite = sprite;
+ this.origin = Vector2.zero;
+ this._sheetIndex = 0;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return this.sprite.size;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "center",
+ {
+ get: function () {
+ return this.sprite.center;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "sheetIndex",
+ {
+ get: function () {
+ return this._sheetIndex;
+ },
+ set: function (value) {
+ if (value >= 0 && value < this.sprite.nrSheetElements)
+ this._sheetIndex = value;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "boundingBox",
+ {
+ get: function () {
+ var leftTop = this.worldPosition.subtractFrom((this.origin));
+ return new Rectangle(leftTop.x, leftTop.y, this.width, this.height);
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (this._visible)
+ this.sprite.draw(this.worldPosition, this.origin, this._sheetIndex);
+};
diff --git a/18_SpriteSheets/PenguinPairs1/geom/Rectangle.js b/18_SpriteSheets/PenguinPairs1/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/geom/Vector2.js b/18_SpriteSheets/PenguinPairs1/geom/Vector2.js
new file mode 100644
index 0000000..04bf6a8
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor == Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor == Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor == Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor == Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/input/ButtonState.js b/18_SpriteSheets/PenguinPairs1/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/input/Keyboard.js b/18_SpriteSheets/PenguinPairs1/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/18_SpriteSheets/PenguinPairs1/input/Mouse.js b/18_SpriteSheets/PenguinPairs1/input/Mouse.js
new file mode 100644
index 0000000..662f762
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/input/Touch.js b/18_SpriteSheets/PenguinPairs1/input/Touch.js
new file mode 100644
index 0000000..7adb4ce
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/input/Touch.js
@@ -0,0 +1,108 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return Vector2.zero;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/system/Color.js b/18_SpriteSheets/PenguinPairs1/system/Color.js
new file mode 100644
index 0000000..cde103d
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+};
\ No newline at end of file
diff --git a/18_SpriteSheets/PenguinPairs1/system/Keys.js b/18_SpriteSheets/PenguinPairs1/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/18_SpriteSheets/PenguinPairs1/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/19_MenusSettings/LAB.min.js b/19_MenusSettings/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/19_MenusSettings/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/Canvas2D.js b/19_MenusSettings/PenguinPairs2/Canvas2D.js
new file mode 100644
index 0000000..8df997b
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/Canvas2D.js
@@ -0,0 +1,149 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._pixeldrawingCanvas = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+
+ this._pixeldrawingCanvas = document.createElement('canvas');
+
+ window.onresize = this.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect, mirror) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ if (mirror) {
+ this._canvasContext.scale(scale * canvasScale.x * -1, scale * canvasScale.y);
+ this._canvasContext.translate(-position.x - sourceRect.width, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ sourceRect.width - origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ else {
+ this._canvasContext.scale(scale * canvasScale.x, scale * canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawPixel = function (x, y, color) {
+ var canvasscale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasscale.x, canvasscale.y);
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.fillRect(x, y, 1, 1);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawRectangle = function (x, y, width, height) {
+ var canvasScale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.strokeRect(x, y, width, height);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/Game.js b/19_MenusSettings/PenguinPairs2/Game.js
new file mode 100644
index 0000000..bab8420
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/Game.js
@@ -0,0 +1,103 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ this.gameWorld = null;
+
+ // for debugging
+ this.log = "";
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.loadSprite = function (imageName) {
+ console.log("Loading sprite: " + imageName);
+ var image = new Image();
+ image.src = imageName;
+ this._spritesStillLoading += 1;
+ this._totalSprites += 1;
+ image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+ return image;
+};
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ Game.gameWorld.handleInput(delta);
+ Game.gameWorld.update(delta);
+ Canvas2D.clear();
+ Game.gameWorld.draw();
+
+ // displaying the number of touches
+ Canvas2D.drawText(Game.log);
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/LAB.min.js b/19_MenusSettings/PenguinPairs2/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/Math.js b/19_MenusSettings/PenguinPairs2/Math.js
new file mode 100644
index 0000000..5e1da3b
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/Math.js
@@ -0,0 +1,23 @@
+"use strict";
+
+if (!Math.sign) {
+ Math.sign = function (value) {
+ if (value > 0)
+ return 1;
+ else if (value < 0)
+ return -1;
+ else
+ return 0;
+ };
+}
+
+if (!Math.clamp) {
+ Math.clamp = function (value, min, max) {
+ if (value < min)
+ return min;
+ else if (value > max)
+ return max;
+ else
+ return value;
+ };
+}
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/PenguinPairs.html b/19_MenusSettings/PenguinPairs2/PenguinPairs.html
new file mode 100644
index 0000000..072eba5
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/PenguinPairs.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+ Penguin Pairs
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/PenguinPairs.js b/19_MenusSettings/PenguinPairs2/PenguinPairs.js
new file mode 100644
index 0000000..2cbc880
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/PenguinPairs.js
@@ -0,0 +1,49 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+var GameSettings = {
+ hints: true
+};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return new SpriteSheet("../../assets/PenguinPairs/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/PenguinPairs/sounds/" + sound, looping);
+ };
+
+ sprites.background_options = loadSprite("spr_background_options.jpg");
+ sprites.button_back = loadSprite("spr_button_back.jpg");
+ sprites.button_offon = loadSprite("spr_button_offon@2.png");
+ sprites.slider_bar = loadSprite("spr_slider_bar.jpg");
+ sprites.slider_button = loadSprite("spr_slider_button.jpg");
+
+ sounds.music = loadSound("snd_music", true);
+};
+
+Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // create the game world
+ Game.gameWorld = new PenguinPairsGameWorld();
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/Sound.js b/19_MenusSettings/PenguinPairs2/Sound.js
new file mode 100644
index 0000000..5d9671b
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function (value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function () {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/SpriteSheet.js b/19_MenusSettings/PenguinPairs2/SpriteSheet.js
new file mode 100644
index 0000000..7486243
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/SpriteSheet.js
@@ -0,0 +1,79 @@
+"use strict";
+
+function SpriteSheet(imageName) {
+ console.log("Loading sprite: " + imageName);
+ Game._spritesStillLoading += 1;
+ Game._totalSprites += 1;
+
+ this._image = new Image();
+ this._image.src = imageName;
+ this._sheetColumns = 1;
+ this._sheetRows = 1;
+ this._collisionMask = null;
+
+ var sprite = this;
+ this._image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+
+ // determine the number of sheet rows and columns
+ var pathSplit = imageName.split('/');
+ var fileName = pathSplit[pathSplit.length - 1];
+ var fileSplit = fileName.split("/")[0].split(".")[0].split("@");
+ if (fileSplit.length <= 1)
+ return;
+ var colRow = fileSplit[fileSplit.length - 1].split("x");
+ this._sheetColumns = colRow[0];
+ if (colRow.length === 2)
+ this._sheetRows = colRow[1];
+}
+
+Object.defineProperty(SpriteSheet.prototype, "image",
+ {
+ get: function () {
+ return this._image;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "width",
+ {
+ get: function () {
+ return this._image.width / this._sheetColumns;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "height",
+ {
+ get: function () {
+ return this._image.height / this._sheetRows;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "center",
+ {
+ get: function () {
+ return this.size.divideBy(2);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "nrSheetElements",
+ {
+ get: function () {
+ return this._sheetRows * this._sheetColumns;
+ }
+ });
+
+SpriteSheet.prototype.draw = function (position, origin, sheetIndex, mirror) {
+ var columnIndex = sheetIndex % this._sheetColumns;
+ var rowIndex = Math.floor(sheetIndex / this._sheetColumns) % this._sheetRows;
+ var imagePart = new Rectangle(columnIndex * this.width, rowIndex * this.height,
+ this.width, this.height);
+ Canvas2D.drawImage(this._image, position, 0, 1, origin, imagePart, mirror);
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/game-layout.css b/19_MenusSettings/PenguinPairs2/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/gameobjects/GameObject.js b/19_MenusSettings/PenguinPairs2/gameobjects/GameObject.js
new file mode 100644
index 0000000..09a33ff
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/gameobjects/GameObject.js
@@ -0,0 +1,58 @@
+"use strict";
+
+function GameObject(layer, id) {
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
+
+GameObject.prototype.handleInput = function (delta) {
+};
+
+GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+GameObject.prototype.draw = function () {
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/gameobjects/GameObjectList.js b/19_MenusSettings/PenguinPairs2/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..b153446
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/gameobjects/GameObjectList.js
@@ -0,0 +1,79 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer, id) {
+ GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/gameobjects/PenguinPairsGameWorld.js b/19_MenusSettings/PenguinPairs2/gameobjects/PenguinPairsGameWorld.js
new file mode 100644
index 0000000..76dd019
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/gameobjects/PenguinPairsGameWorld.js
@@ -0,0 +1,42 @@
+"use strict";
+
+function PenguinPairsGameWorld(layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ var background = new SpriteGameObject(sprites.background_options, ID.layer_background);
+ this.add(background);
+
+ var onOffLabel = new Label("Arial", "60px", ID.layer_overlays);
+ onOffLabel.text = "Hints";
+ onOffLabel.position = new Vector2(150, 360);
+ onOffLabel.color = Color.darkBlue;
+ this.add(onOffLabel);
+
+ this.onOffButton = new OnOffButton(sprites.button_offon, ID.layer_overlays);
+ this.onOffButton.position = new Vector2(650, 340);
+ this.onOffButton.on = GameSettings.hints;
+ this.add(this.onOffButton);
+
+ var musicText = new Label("Arial", "60px", ID.layer_overlays);
+ musicText.text = "Music volume";
+ musicText.position = new Vector2(150, 490);
+ musicText.color = Color.darkBlue;
+ this.add(musicText);
+
+ this.musicSlider = new Slider(sprites.slider_bar, sprites.slider_button, ID.layer_overlays);
+ this.musicSlider.position = new Vector2(650, 500);
+ this.musicSlider.value = sounds.music.volume;
+ this.add(this.musicSlider);
+
+ this.backButton = new Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+PenguinPairsGameWorld.prototype = Object.create(GameObjectList.prototype);
+
+PenguinPairsGameWorld.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ sounds.music.volume = this.musicSlider.value;
+ GameSettings.hints = this.onOffButton.on;
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/gameobjects/SpriteGameObject.js b/19_MenusSettings/PenguinPairs2/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..c14bde2
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/gameobjects/SpriteGameObject.js
@@ -0,0 +1,86 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.sprite = sprite;
+
+ this.origin = Vector2.zero;
+ this.mirror = false;
+ this._sheetIndex = 0;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return this.sprite.size;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "center",
+ {
+ get: function () {
+ return this.sprite.center;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "sheetIndex",
+ {
+ get: function () {
+ return this._sheetIndex;
+ },
+ set: function (value) {
+ if (value >= 0 && value < this.sprite.nrSheetElements)
+ this._sheetIndex = value;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "boundingBox",
+ {
+ get: function () {
+ var leftTop = this.worldPosition.subtractFrom((this.origin));
+ return new Rectangle(leftTop.x, leftTop.y, this.width, this.height);
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (this._visible)
+ this.sprite.draw(this.worldPosition, this.origin, this._sheetIndex, this.mirror);
+};
diff --git a/19_MenusSettings/PenguinPairs2/geom/Rectangle.js b/19_MenusSettings/PenguinPairs2/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/geom/Vector2.js b/19_MenusSettings/PenguinPairs2/geom/Vector2.js
new file mode 100644
index 0000000..04bf6a8
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor == Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor == Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor == Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor == Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/gui/Button.js b/19_MenusSettings/PenguinPairs2/gui/Button.js
new file mode 100644
index 0000000..d1d9d63
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/gui/Button.js
@@ -0,0 +1,18 @@
+"use strict";
+
+function Button(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+
+ this.pressed = false;
+ this.down = false;
+}
+
+Button.prototype = Object.create(SpriteGameObject.prototype);
+
+Button.prototype.handleInput = function (delta) {
+ var boundingBox = this.boundingBox;
+ this.pressed = this.visible && (Touch.containsTouchPress(boundingBox) ||
+ Mouse.containsMousePress(boundingBox));
+ this.down = this.visible && (Touch.containsTouch(boundingBox) ||
+ Mouse.containsMouseDown(boundingBox));
+};
diff --git a/19_MenusSettings/PenguinPairs2/gui/Label.js b/19_MenusSettings/PenguinPairs2/gui/Label.js
new file mode 100644
index 0000000..41c5af6
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/gui/Label.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function calculateTextSize(fontname, fontsize, text) {
+ var div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = -1000;
+ div.style.top = -1000;
+ document.body.appendChild(div);
+ text = typeof text !== 'undefined' ? text : "M";
+ div.style.fontSize = "" + fontsize;
+ div.style.fontFamily = fontname;
+ div.innerHTML = text;
+ var size = new Vector2(div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+ return size;
+}
+
+function Label(fontname, fontsize, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.color = Color.black;
+ this.origin = Vector2.zero;
+ this._fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ this._fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+ this._contents = "";
+ this._align = "left";
+ this._size = Vector2.zero;
+}
+
+Label.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(Label.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "width",
+ {
+ get: function () {
+ return this._size.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "height",
+ {
+ get: function () {
+ return this._size.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(Label.prototype, "text",
+ {
+ get: function () {
+ return this._contents;
+ },
+
+ set: function (value) {
+ this._contents = value;
+ this._size = calculateTextSize(this._fontname, this._fontsize, value);
+ }
+ });
+
+Label.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawText(this._contents, this.worldPosition,
+ this.origin, this.color, this._align,
+ this._fontname, this._fontsize);
+};
diff --git a/19_MenusSettings/PenguinPairs2/gui/OnOffButton.js b/19_MenusSettings/PenguinPairs2/gui/OnOffButton.js
new file mode 100644
index 0000000..530a822
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/gui/OnOffButton.js
@@ -0,0 +1,28 @@
+"use strict";
+
+function OnOffButton(sprite, layer) {
+ SpriteGameObject.call(this, sprite, layer);
+}
+
+OnOffButton.prototype = Object.create(SpriteGameObject.prototype);
+
+Object.defineProperty(OnOffButton.prototype, "on",
+ {
+ get: function () {
+ return this.sheetIndex === 1;
+ },
+ set: function (value) {
+ if (value)
+ this.sheetIndex = 1;
+ else
+ this.sheetIndex = 0;
+ }
+ });
+
+OnOffButton.prototype.handleInput = function (delta) {
+ if (!this.visible)
+ return;
+ if (Touch.containsTouchPress(this.boundingBox) ||
+ Mouse.containsMousePress(this.boundingBox))
+ this.sheetIndex = 1 - this.sheetIndex;
+};
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/gui/Slider.js b/19_MenusSettings/PenguinPairs2/gui/Slider.js
new file mode 100644
index 0000000..dfe0ad6
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/gui/Slider.js
@@ -0,0 +1,74 @@
+"use strict";
+
+function Slider(sliderback, sliderfront, layer) {
+ GameObjectList.call(this, layer);
+ this.dragging = false;
+ this.draggingId = -1;
+ this.leftmargin = 5;
+ this.rightmargin = 7;
+
+ this.back = new SpriteGameObject(sliderback);
+ this.front = new SpriteGameObject(sliderfront, 1);
+ this.front.position = new Vector2(this.leftmargin, 8);
+ this.add(this.back);
+ this.add(this.front);
+}
+
+Slider.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(Slider.prototype, "value",
+ {
+ get: function () {
+ return (this.front.position.x - this.back.position.x - this.leftmargin) /
+ (this.back.width - this.front.width - this.leftmargin - this.rightmargin);
+ },
+ set: function (value) {
+ var newxpos = value * (this.back.width - this.front.width - this.leftmargin - this.rightmargin)
+ + this.back.position.x + this.leftmargin;
+ this.front.position = new Vector2(newxpos, this.front.position.y);
+ }
+ });
+
+Slider.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (Touch.isTouchDevice) {
+ this.handleInputTouch(delta);
+ } else {
+ this.handleInputMouse(delta);
+ }
+};
+
+Slider.prototype.handleInputMouse = function (delta) {
+ if (!Mouse.left.down) {
+ this.dragging = false;
+ this.draggingId = -1;
+ return;
+ }
+ if (this.back.boundingBox.contains(Mouse.position))
+ this.dragging = true;
+ if (this.dragging) {
+ this.front.position = new Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(Mouse.position.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ }
+};
+
+Slider.prototype.handleInputTouch = function (delta) {
+ if (!Touch.isTouching) {
+ this.dragging = false;
+ this.draggingId = -1;
+ return;
+ }
+ if (Touch.containsTouch(this.back.boundingBox)) {
+ this.draggingId = Touch.getIndexInRect(this.back.boundingBox);
+ this.dragging = true;
+ }
+ if (this.dragging) {
+ var touchPos = Touch.getPosition(this.draggingId);
+ this.front.position = new Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(touchPos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ }
+};
diff --git a/19_MenusSettings/PenguinPairs2/input/ButtonState.js b/19_MenusSettings/PenguinPairs2/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/input/Keyboard.js b/19_MenusSettings/PenguinPairs2/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/19_MenusSettings/PenguinPairs2/input/Mouse.js b/19_MenusSettings/PenguinPairs2/input/Mouse.js
new file mode 100644
index 0000000..662f762
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/input/Touch.js b/19_MenusSettings/PenguinPairs2/input/Touch.js
new file mode 100644
index 0000000..7adb4ce
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/input/Touch.js
@@ -0,0 +1,108 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return Vector2.zero;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/system/Color.js b/19_MenusSettings/PenguinPairs2/system/Color.js
new file mode 100644
index 0000000..19ee82a
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+ var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+ };
\ No newline at end of file
diff --git a/19_MenusSettings/PenguinPairs2/system/Keys.js b/19_MenusSettings/PenguinPairs2/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/19_MenusSettings/PenguinPairs2/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/LAB.min.js b/20_GameStateManagement/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/20_GameStateManagement/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/Canvas2D.js b/20_GameStateManagement/PenguinPairs3/Canvas2D.js
new file mode 100644
index 0000000..8df997b
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/Canvas2D.js
@@ -0,0 +1,149 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._pixeldrawingCanvas = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+
+ this._pixeldrawingCanvas = document.createElement('canvas');
+
+ window.onresize = this.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect, mirror) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ if (mirror) {
+ this._canvasContext.scale(scale * canvasScale.x * -1, scale * canvasScale.y);
+ this._canvasContext.translate(-position.x - sourceRect.width, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ sourceRect.width - origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ else {
+ this._canvasContext.scale(scale * canvasScale.x, scale * canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawPixel = function (x, y, color) {
+ var canvasscale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasscale.x, canvasscale.y);
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.fillRect(x, y, 1, 1);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawRectangle = function (x, y, width, height) {
+ var canvasScale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.strokeRect(x, y, width, height);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/Game.js b/20_GameStateManagement/PenguinPairs3/Game.js
new file mode 100644
index 0000000..65f776e
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/Game.js
@@ -0,0 +1,85 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ GameStateManager.handleInput(delta);
+ GameStateManager.update(delta);
+ Canvas2D.clear();
+ GameStateManager.draw();
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/GameStateManager.js b/20_GameStateManagement/PenguinPairs3/GameStateManager.js
new file mode 100644
index 0000000..50fd349
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/GameStateManager.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function GameStateManager_Singleton() {
+ this._gameStates = [];
+ this._currentGameState = null;
+}
+
+GameStateManager_Singleton.prototype.add = function (gamestate) {
+ this._gameStates.push(gamestate);
+ this._currentGameState = gamestate;
+ return this._gameStates.length - 1;
+};
+
+GameStateManager_Singleton.prototype.get = function (id) {
+ if (id < 0 || id >= this._gameStates.length)
+ return null;
+ else
+ return this._gameStates[id];
+};
+
+GameStateManager_Singleton.prototype.switchTo = function (id) {
+ this._currentGameState = this.get(id);
+};
+
+GameStateManager_Singleton.prototype.handleInput = function (delta) {
+ if (this._currentGameState !== null)
+ this._currentGameState.handleInput(delta);
+};
+
+GameStateManager_Singleton.prototype.update = function (delta) {
+ if (this._currentGameState !== null)
+ this._currentGameState.update(delta);
+};
+
+GameStateManager_Singleton.prototype.draw = function () {
+ if (this._currentGameState !== null)
+ this._currentGameState.draw();
+};
+
+GameStateManager_Singleton.prototype.reset = function () {
+ if (this._currentGameState !== null)
+ this._currentGameState.reset();
+};
+
+var GameStateManager = new GameStateManager_Singleton();
diff --git a/20_GameStateManagement/PenguinPairs3/LAB.min.js b/20_GameStateManagement/PenguinPairs3/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/Math.js b/20_GameStateManagement/PenguinPairs3/Math.js
new file mode 100644
index 0000000..5e1da3b
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/Math.js
@@ -0,0 +1,23 @@
+"use strict";
+
+if (!Math.sign) {
+ Math.sign = function (value) {
+ if (value > 0)
+ return 1;
+ else if (value < 0)
+ return -1;
+ else
+ return 0;
+ };
+}
+
+if (!Math.clamp) {
+ Math.clamp = function (value, min, max) {
+ if (value < min)
+ return min;
+ else if (value > max)
+ return max;
+ else
+ return value;
+ };
+}
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/PenguinPairs.html b/20_GameStateManagement/PenguinPairs3/PenguinPairs.html
new file mode 100644
index 0000000..9d4ec30
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/PenguinPairs.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+ Penguin Pairs
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/PenguinPairs.js b/20_GameStateManagement/PenguinPairs3/PenguinPairs.js
new file mode 100644
index 0000000..f4a76a8
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/PenguinPairs.js
@@ -0,0 +1,69 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+var GameSettings = {
+ hints: true
+};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return new SpriteSheet("../../assets/PenguinPairs/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/PenguinPairs/sounds/" + sound, looping);
+ };
+
+ sprites.background_title = loadSprite("spr_background_title.jpg");
+ sprites.background_help = loadSprite("spr_background_help.jpg");
+ sprites.background_level = loadSprite("spr_background_level.jpg");
+ sprites.background_levelselect = loadSprite("spr_background_levelselect.jpg");
+ sprites.background_options = loadSprite("spr_background_options.jpg");
+ sprites.button_back = loadSprite("spr_button_back.jpg");
+ sprites.button_help = loadSprite("spr_button_help.jpg");
+ sprites.button_hint = loadSprite("spr_button_hint.png");
+ sprites.button_offon = loadSprite("spr_button_offon@2.png");
+ sprites.button_options = loadSprite("spr_button_options.jpg");
+ sprites.button_play = loadSprite("spr_button_play.jpg");
+ sprites.button_quit = loadSprite("spr_button_quit.png");
+ sprites.button_retry = loadSprite("spr_button_retry.png");
+ sprites.help = loadSprite("spr_help.jpg");
+ sprites.level_unsolved = loadSprite("spr_level_unsolved.png");
+ sprites.level_solved = loadSprite("spr_level_solved@6.png");
+ sprites.level_locked = loadSprite("spr_lock.png");
+ sprites.slider_bar = loadSprite("spr_slider_bar.jpg");
+ sprites.slider_button = loadSprite("spr_slider_button.jpg");
+
+ sounds.music = loadSound("snd_music", true);
+};
+
+Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // create the different game modes
+ ID.game_state_title = GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = GameStateManager.add(new HelpState());
+ ID.game_state_options = GameStateManager.add(new OptionsMenuState());
+ ID.game_state_levelselect = GameStateManager.add(new LevelMenuState());
+
+ // set the current game mode
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/Sound.js b/20_GameStateManagement/PenguinPairs3/Sound.js
new file mode 100644
index 0000000..5d9671b
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function (value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function () {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/SpriteSheet.js b/20_GameStateManagement/PenguinPairs3/SpriteSheet.js
new file mode 100644
index 0000000..7486243
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/SpriteSheet.js
@@ -0,0 +1,79 @@
+"use strict";
+
+function SpriteSheet(imageName) {
+ console.log("Loading sprite: " + imageName);
+ Game._spritesStillLoading += 1;
+ Game._totalSprites += 1;
+
+ this._image = new Image();
+ this._image.src = imageName;
+ this._sheetColumns = 1;
+ this._sheetRows = 1;
+ this._collisionMask = null;
+
+ var sprite = this;
+ this._image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+
+ // determine the number of sheet rows and columns
+ var pathSplit = imageName.split('/');
+ var fileName = pathSplit[pathSplit.length - 1];
+ var fileSplit = fileName.split("/")[0].split(".")[0].split("@");
+ if (fileSplit.length <= 1)
+ return;
+ var colRow = fileSplit[fileSplit.length - 1].split("x");
+ this._sheetColumns = colRow[0];
+ if (colRow.length === 2)
+ this._sheetRows = colRow[1];
+}
+
+Object.defineProperty(SpriteSheet.prototype, "image",
+ {
+ get: function () {
+ return this._image;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "width",
+ {
+ get: function () {
+ return this._image.width / this._sheetColumns;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "height",
+ {
+ get: function () {
+ return this._image.height / this._sheetRows;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "center",
+ {
+ get: function () {
+ return this.size.divideBy(2);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "nrSheetElements",
+ {
+ get: function () {
+ return this._sheetRows * this._sheetColumns;
+ }
+ });
+
+SpriteSheet.prototype.draw = function (position, origin, sheetIndex, mirror) {
+ var columnIndex = sheetIndex % this._sheetColumns;
+ var rowIndex = Math.floor(sheetIndex / this._sheetColumns) % this._sheetRows;
+ var imagePart = new Rectangle(columnIndex * this.width, rowIndex * this.height,
+ this.width, this.height);
+ Canvas2D.drawImage(this._image, position, 0, 1, origin, imagePart, mirror);
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/game-layout.css b/20_GameStateManagement/PenguinPairs3/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/gameobjects/GameObject.js b/20_GameStateManagement/PenguinPairs3/gameobjects/GameObject.js
new file mode 100644
index 0000000..09a33ff
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/gameobjects/GameObject.js
@@ -0,0 +1,58 @@
+"use strict";
+
+function GameObject(layer, id) {
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
+
+GameObject.prototype.handleInput = function (delta) {
+};
+
+GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
+
+GameObject.prototype.draw = function () {
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/gameobjects/GameObjectList.js b/20_GameStateManagement/PenguinPairs3/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..b153446
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/gameobjects/GameObjectList.js
@@ -0,0 +1,79 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer, id) {
+ GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/gameobjects/SpriteGameObject.js b/20_GameStateManagement/PenguinPairs3/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..ad72ce1
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/gameobjects/SpriteGameObject.js
@@ -0,0 +1,86 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.sprite = sprite;
+
+ this.origin = Vector2.zero;
+ this.mirror = false;
+ this._sheetIndex = 0;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return this.sprite.size;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "center",
+ {
+ get: function () {
+ return this.sprite.center;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "sheetIndex",
+ {
+ get: function () {
+ return this._sheetIndex;
+ },
+ set: function (value) {
+ if (value >= 0)
+ this._sheetIndex = value % this.sprite.nrSheetElements;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "boundingBox",
+ {
+ get: function () {
+ var leftTop = this.worldPosition.subtractFrom((this.origin));
+ return new Rectangle(leftTop.x, leftTop.y, this.width, this.height);
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (this._visible)
+ this.sprite.draw(this.worldPosition, this.origin, this._sheetIndex, this.mirror);
+};
diff --git a/20_GameStateManagement/PenguinPairs3/geom/Rectangle.js b/20_GameStateManagement/PenguinPairs3/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/geom/Vector2.js b/20_GameStateManagement/PenguinPairs3/geom/Vector2.js
new file mode 100644
index 0000000..04bf6a8
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor == Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor == Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor == Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor == Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/gui/Button.js b/20_GameStateManagement/PenguinPairs3/gui/Button.js
new file mode 100644
index 0000000..d1d9d63
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/gui/Button.js
@@ -0,0 +1,18 @@
+"use strict";
+
+function Button(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+
+ this.pressed = false;
+ this.down = false;
+}
+
+Button.prototype = Object.create(SpriteGameObject.prototype);
+
+Button.prototype.handleInput = function (delta) {
+ var boundingBox = this.boundingBox;
+ this.pressed = this.visible && (Touch.containsTouchPress(boundingBox) ||
+ Mouse.containsMousePress(boundingBox));
+ this.down = this.visible && (Touch.containsTouch(boundingBox) ||
+ Mouse.containsMouseDown(boundingBox));
+};
diff --git a/20_GameStateManagement/PenguinPairs3/gui/Label.js b/20_GameStateManagement/PenguinPairs3/gui/Label.js
new file mode 100644
index 0000000..41c5af6
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/gui/Label.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function calculateTextSize(fontname, fontsize, text) {
+ var div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = -1000;
+ div.style.top = -1000;
+ document.body.appendChild(div);
+ text = typeof text !== 'undefined' ? text : "M";
+ div.style.fontSize = "" + fontsize;
+ div.style.fontFamily = fontname;
+ div.innerHTML = text;
+ var size = new Vector2(div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+ return size;
+}
+
+function Label(fontname, fontsize, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.color = Color.black;
+ this.origin = Vector2.zero;
+ this._fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ this._fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+ this._contents = "";
+ this._align = "left";
+ this._size = Vector2.zero;
+}
+
+Label.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(Label.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "width",
+ {
+ get: function () {
+ return this._size.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "height",
+ {
+ get: function () {
+ return this._size.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(Label.prototype, "text",
+ {
+ get: function () {
+ return this._contents;
+ },
+
+ set: function (value) {
+ this._contents = value;
+ this._size = calculateTextSize(this._fontname, this._fontsize, value);
+ }
+ });
+
+Label.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawText(this._contents, this.worldPosition,
+ this.origin, this.color, this._align,
+ this._fontname, this._fontsize);
+};
diff --git a/20_GameStateManagement/PenguinPairs3/gui/LevelButton.js b/20_GameStateManagement/PenguinPairs3/gui/LevelButton.js
new file mode 100644
index 0000000..c7470bd
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/gui/LevelButton.js
@@ -0,0 +1,47 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new SpriteGameObject(sprites.level_solved, ID.layer_overlays);
+ this._levelUnsolved = new SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+ this._levelLocked = new SpriteGameObject(sprites.level_locked, ID.layer_overlays_2);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+ this.add(this._levelUnsolved);
+ this.add(this._levelLocked);
+
+ var textLabel = new Label("Arial", "20px", ID.layer_overlays_1);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new Vector2(this._levelSolved.width - textLabel.width,
+ this._levelSolved.height - textLabel.height + 50).divideBy(2);
+ textLabel.color = Color.black;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (Touch.isTouchDevice)
+ this.pressed = this.visible && Touch.containsTouchPress(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(Mouse.position);
+};
diff --git a/20_GameStateManagement/PenguinPairs3/gui/OnOffButton.js b/20_GameStateManagement/PenguinPairs3/gui/OnOffButton.js
new file mode 100644
index 0000000..78d25bb
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/gui/OnOffButton.js
@@ -0,0 +1,28 @@
+"use strict";
+
+function OnOffButton(sprite, layer) {
+ SpriteGameObject.call(this, sprite, layer);
+}
+
+OnOffButton.prototype = Object.create(SpriteGameObject.prototype);
+
+Object.defineProperty(OnOffButton.prototype, "on",
+ {
+ get: function () {
+ return this.sheetIndex === 1;
+ },
+ set: function (value) {
+ if (value)
+ this.sheetIndex = 1;
+ else
+ this.sheetIndex = 0;
+ }
+ });
+
+OnOffButton.prototype.handleInput = function (delta) {
+ if (!this.visible)
+ return;
+ if (Touch.containsTouchPress(this.boundingBox) ||
+ Mouse.containsMousePress(this.boundingBox))
+ this.sheetIndex = (this.sheetIndex + 1) % 2;
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/gui/Slider.js b/20_GameStateManagement/PenguinPairs3/gui/Slider.js
new file mode 100644
index 0000000..20dca1f
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/gui/Slider.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function Slider(sliderback, sliderfront, layer) {
+ GameObjectList.call(this, layer);
+ this.dragging = false;
+ this.draggingId = -1;
+ this.leftmargin = 5;
+ this.rightmargin = 7;
+
+ this.back = new SpriteGameObject(sliderback);
+ this.front = new SpriteGameObject(sliderfront, 1);
+ this.front.position = new Vector2(this.leftmargin, 8);
+ this.add(this.back);
+ this.add(this.front);
+}
+
+Slider.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(Slider.prototype, "value",
+ {
+ get: function () {
+ return (this.front.position.x - this.back.position.x - this.leftmargin) /
+ (this.back.width - this.front.width - this.leftmargin - this.rightmargin);
+ },
+ set: function (value) {
+ var newxpos = value * (this.back.width - this.front.width - this.leftmargin - this.rightmargin)
+ + this.back.position.x + this.leftmargin;
+ this.front.position = new Vector2(newxpos, this.front.position.y);
+ }
+ });
+
+Slider.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (Mouse.left.down || Touch.isTouching) {
+ if (Touch.isTouchDevice) {
+ if (Touch.containsTouch(this.back.boundingBox))
+ this.draggingId = Touch.getIndexInRect((this.back.boundingBox));
+ if (Touch.containsTouch(this.back.boundingBox) || this.dragging) {
+ var touchPos = Touch.getPosition(this.draggingId);
+ this.front.position = new Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(touchPos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ this.dragging = true;
+ }
+ } else {
+ var mousePos = Mouse.position;
+ if (this.back.boundingBox.contains(mousePos) || this.dragging) {
+ this.front.position = new Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(mousePos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ this.dragging = true;
+ }
+ }
+ }
+ else {
+ this.dragging = false;
+ this.draggingId = -1;
+ }
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/input/ButtonState.js b/20_GameStateManagement/PenguinPairs3/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/input/Keyboard.js b/20_GameStateManagement/PenguinPairs3/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/20_GameStateManagement/PenguinPairs3/input/Mouse.js b/20_GameStateManagement/PenguinPairs3/input/Mouse.js
new file mode 100644
index 0000000..662f762
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/input/Touch.js b/20_GameStateManagement/PenguinPairs3/input/Touch.js
new file mode 100644
index 0000000..7adb4ce
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/input/Touch.js
@@ -0,0 +1,108 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return Vector2.zero;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/states/HelpState.js b/20_GameStateManagement/PenguinPairs3/states/HelpState.js
new file mode 100644
index 0000000..60f3ca6
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/states/HelpState.js
@@ -0,0 +1,22 @@
+"use strict";
+
+function HelpState(layer) {
+ GameObjectList.call(this, layer);
+
+ // the background
+ var background = new SpriteGameObject(sprites.background_help);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new Button(sprites.button_back, 100);
+ this.backButton.position = new Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+HelpState.prototype = Object.create(GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/states/LevelMenuState.js b/20_GameStateManagement/PenguinPairs3/states/LevelMenuState.js
new file mode 100644
index 0000000..f7cad35
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/states/LevelMenuState.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ this.background = new SpriteGameObject(sprites.background_levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0; i < 12; i += 1) {
+ var row = Math.floor(i / 5);
+ var column = i % 5;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new Vector2(column * (level.width + 30) + 155,
+ row * (level.height + 5) + 230);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i += 1) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ // start playing the level
+ }
+ else if (this.back.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/states/OptionsMenuState.js b/20_GameStateManagement/PenguinPairs3/states/OptionsMenuState.js
new file mode 100644
index 0000000..f4ea732
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/states/OptionsMenuState.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function OptionsMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ var background = new SpriteGameObject(sprites.background_options, ID.layer_background);
+ this.add(background);
+
+ var onOffLabel = new Label("Arial", "60px", ID.layer_overlays);
+ onOffLabel.text = "Hints";
+ onOffLabel.position = new Vector2(150, 360);
+ onOffLabel.color = Color.darkBlue;
+ this.add(onOffLabel);
+
+ this.onOffButton = new OnOffButton(sprites.button_offon, ID.layer_overlays);
+ this.onOffButton.position = new Vector2(650, 340);
+ this.onOffButton.on = GameSettings.hints;
+ this.add(this.onOffButton);
+
+ var musicText = new Label("Arial", "60px", ID.layer_overlays);
+ musicText.text = "Music volume";
+ musicText.position = new Vector2(150, 490);
+ musicText.color = Color.darkBlue;
+ this.add(musicText);
+
+ this.musicSlider = new Slider(sprites.slider_bar, sprites.slider_button, ID.layer_overlays);
+ this.musicSlider.position = new Vector2(650, 500);
+ this.musicSlider.value = sounds.music.volume;
+ this.add(this.musicSlider);
+
+ this.backButton = new Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+OptionsMenuState.prototype = Object.create(GameObjectList.prototype);
+
+OptionsMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
+
+OptionsMenuState.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ sounds.music.volume = this.musicSlider.value;
+ GameSettings.hints = this.onOffButton.on;
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/states/TitleMenuState.js b/20_GameStateManagement/PenguinPairs3/states/TitleMenuState.js
new file mode 100644
index 0000000..570edba
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/states/TitleMenuState.js
@@ -0,0 +1,31 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ this.add(new SpriteGameObject(sprites.background_title, ID.layer_background));
+
+ this.playButton = new Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new Vector2(415, 540);
+ this.add(this.playButton);
+
+ this.optionsButton = new Button(sprites.button_options, ID.layer_overlays);
+ this.optionsButton.position = new Vector2(415, 650);
+ this.add(this.optionsButton);
+
+ this.helpButton = new Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new Vector2(415, 760);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ GameStateManager.switchTo(ID.game_state_help);
+ else if (this.optionsButton.pressed)
+ GameStateManager.switchTo(ID.game_state_options);
+};
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/system/Color.js b/20_GameStateManagement/PenguinPairs3/system/Color.js
new file mode 100644
index 0000000..19ee82a
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+ var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+ };
\ No newline at end of file
diff --git a/20_GameStateManagement/PenguinPairs3/system/Keys.js b/20_GameStateManagement/PenguinPairs3/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/20_GameStateManagement/PenguinPairs3/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/LAB.min.js b/21_StoringRecallingGameData/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/21_StoringRecallingGameData/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/Canvas2D.js b/21_StoringRecallingGameData/PenguinPairs4/Canvas2D.js
new file mode 100644
index 0000000..8df997b
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/Canvas2D.js
@@ -0,0 +1,149 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._pixeldrawingCanvas = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+
+ this._pixeldrawingCanvas = document.createElement('canvas');
+
+ window.onresize = this.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect, mirror) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ if (mirror) {
+ this._canvasContext.scale(scale * canvasScale.x * -1, scale * canvasScale.y);
+ this._canvasContext.translate(-position.x - sourceRect.width, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ sourceRect.width - origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ else {
+ this._canvasContext.scale(scale * canvasScale.x, scale * canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawPixel = function (x, y, color) {
+ var canvasscale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasscale.x, canvasscale.y);
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.fillRect(x, y, 1, 1);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawRectangle = function (x, y, width, height) {
+ var canvasScale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.strokeRect(x, y, width, height);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/Game.js b/21_StoringRecallingGameData/PenguinPairs4/Game.js
new file mode 100644
index 0000000..65f776e
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/Game.js
@@ -0,0 +1,85 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ GameStateManager.handleInput(delta);
+ GameStateManager.update(delta);
+ Canvas2D.clear();
+ GameStateManager.draw();
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/GameStateManager.js b/21_StoringRecallingGameData/PenguinPairs4/GameStateManager.js
new file mode 100644
index 0000000..df07f76
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/GameStateManager.js
@@ -0,0 +1,47 @@
+"use strict";
+
+function GameStateManager_Singleton() {
+ this._gameStates = [];
+ this._currentGameState = null;
+}
+
+GameStateManager_Singleton.prototype.add = function (gamestate) {
+ this._gameStates.push(gamestate);
+ this._currentGameState = gamestate;
+ return this._gameStates.length - 1;
+};
+
+GameStateManager_Singleton.prototype.get = function (id) {
+ if (id < 0 || id >= this._gameStates.length)
+ return null;
+ else
+ return this._gameStates[id];
+};
+
+GameStateManager_Singleton.prototype.switchTo = function (id) {
+ if (id < 0 || id >= this._gameStates.length)
+ return;
+ this._currentGameState = this._gameStates[id];
+};
+
+GameStateManager_Singleton.prototype.handleInput = function (delta) {
+ if (this._currentGameState != null)
+ this._currentGameState.handleInput(delta);
+};
+
+GameStateManager_Singleton.prototype.update = function (delta) {
+ if (this._currentGameState != null)
+ this._currentGameState.update(delta);
+};
+
+GameStateManager_Singleton.prototype.draw = function () {
+ if (this._currentGameState != null)
+ this._currentGameState.draw();
+};
+
+GameStateManager_Singleton.prototype.reset = function () {
+ if (this._currentGameState != null)
+ this._currentGameState.reset();
+};
+
+var GameStateManager = new GameStateManager_Singleton();
diff --git a/21_StoringRecallingGameData/PenguinPairs4/IGameLoopObject.js b/21_StoringRecallingGameData/PenguinPairs4/IGameLoopObject.js
new file mode 100644
index 0000000..ef9d8f6
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/IGameLoopObject.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function IGameLoopObject() {
+}
+
+IGameLoopObject.prototype.handleInput = function (delta) {
+};
+
+IGameLoopObject.prototype.update = function (delta) {
+};
+
+IGameLoopObject.prototype.draw = function () {
+};
+
+IGameLoopObject.prototype.reset = function () {
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/LAB.min.js b/21_StoringRecallingGameData/PenguinPairs4/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/Math.js b/21_StoringRecallingGameData/PenguinPairs4/Math.js
new file mode 100644
index 0000000..5e1da3b
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/Math.js
@@ -0,0 +1,23 @@
+"use strict";
+
+if (!Math.sign) {
+ Math.sign = function (value) {
+ if (value > 0)
+ return 1;
+ else if (value < 0)
+ return -1;
+ else
+ return 0;
+ };
+}
+
+if (!Math.clamp) {
+ Math.clamp = function (value, min, max) {
+ if (value < min)
+ return min;
+ else if (value > max)
+ return max;
+ else
+ return value;
+ };
+}
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/PenguinPairs.html b/21_StoringRecallingGameData/PenguinPairs4/PenguinPairs.html
new file mode 100644
index 0000000..8f550b4
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/PenguinPairs.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+ Penguin Pairs
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/PenguinPairs.js b/21_StoringRecallingGameData/PenguinPairs4/PenguinPairs.js
new file mode 100644
index 0000000..74bc8a8
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/PenguinPairs.js
@@ -0,0 +1,92 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+var GameSettings = {
+ hints: true
+};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return new SpriteSheet("../../assets/PenguinPairs/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/PenguinPairs/sounds/" + sound, looping);
+ };
+
+ sprites.arrow = loadSprite("spr_arrow@4.png");
+ sprites.arrow_hover = loadSprite("spr_arrow_hover@4.png");
+ sprites.arrow_hint = loadSprite("spr_arrow_hint@4.png");
+ sprites.background_title = loadSprite("spr_background_title.jpg");
+ sprites.background_help = loadSprite("spr_background_help.jpg");
+ sprites.background_level = loadSprite("spr_background_level.jpg");
+ sprites.background_levelselect = loadSprite("spr_background_levelselect.jpg");
+ sprites.background_options = loadSprite("spr_background_options.jpg");
+ sprites.button_back = loadSprite("spr_button_back.jpg");
+ sprites.button_help = loadSprite("spr_button_help.jpg");
+ sprites.button_hint = loadSprite("spr_button_hint.png");
+ sprites.button_offon = loadSprite("spr_button_offon@2.png");
+ sprites.button_options = loadSprite("spr_button_options.jpg");
+ sprites.button_play = loadSprite("spr_button_play.jpg");
+ sprites.button_quit = loadSprite("spr_button_quit.png");
+ sprites.button_retry = loadSprite("spr_button_retry.png");
+ sprites.field = loadSprite("spr_field@2.png");
+ sprites.frame_goal = loadSprite("spr_frame_goal.jpg");
+ sprites.help = loadSprite("spr_help.jpg");
+ if (Touch.isTouchDevice)
+ sprites.level_finished = loadSprite("spr_level_finished_tap.png");
+ else
+ sprites.level_finished = loadSprite("spr_level_finished_click.png");
+ sprites.level_unsolved = loadSprite("spr_level_unsolved.png");
+ sprites.level_solved = loadSprite("spr_level_solved@6.png");
+ sprites.level_locked = loadSprite("spr_lock.png");
+ sprites.penguin = loadSprite("spr_penguin@8.png");
+ sprites.penguin_boxed = loadSprite("spr_penguin_boxed@8.png");
+ sprites.penguin_empty = loadSprite("spr_penguin_empty.png");
+ sprites.penguin_pairs = loadSprite("spr_penguin_pairs@8.png");
+ sprites.shark = loadSprite("spr_shark.png");
+ sprites.slider_bar = loadSprite("spr_slider_bar.jpg");
+ sprites.slider_button = loadSprite("spr_slider_button.jpg");
+ sprites.wall = loadSprite("spr_wall.png");
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.eat = loadSound("snd_eat");
+ sounds.lost = loadSound("snd_lost");
+ sounds.won = loadSound("snd_won");
+ sounds.pair = loadSound("snd_pair");
+};
+
+Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.tiles = 1;
+
+ // create the different game modes
+ ID.game_state_title = GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = GameStateManager.add(new HelpState());
+ ID.game_state_options = GameStateManager.add(new OptionsMenuState());
+ ID.game_state_playing = GameStateManager.add(new PlayingState());
+ ID.game_state_levelselect = GameStateManager.add(new LevelMenuState());
+
+ // set the current game mode
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/Sound.js b/21_StoringRecallingGameData/PenguinPairs4/Sound.js
new file mode 100644
index 0000000..5d9671b
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function (value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function () {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/SpriteSheet.js b/21_StoringRecallingGameData/PenguinPairs4/SpriteSheet.js
new file mode 100644
index 0000000..7486243
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/SpriteSheet.js
@@ -0,0 +1,79 @@
+"use strict";
+
+function SpriteSheet(imageName) {
+ console.log("Loading sprite: " + imageName);
+ Game._spritesStillLoading += 1;
+ Game._totalSprites += 1;
+
+ this._image = new Image();
+ this._image.src = imageName;
+ this._sheetColumns = 1;
+ this._sheetRows = 1;
+ this._collisionMask = null;
+
+ var sprite = this;
+ this._image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+
+ // determine the number of sheet rows and columns
+ var pathSplit = imageName.split('/');
+ var fileName = pathSplit[pathSplit.length - 1];
+ var fileSplit = fileName.split("/")[0].split(".")[0].split("@");
+ if (fileSplit.length <= 1)
+ return;
+ var colRow = fileSplit[fileSplit.length - 1].split("x");
+ this._sheetColumns = colRow[0];
+ if (colRow.length === 2)
+ this._sheetRows = colRow[1];
+}
+
+Object.defineProperty(SpriteSheet.prototype, "image",
+ {
+ get: function () {
+ return this._image;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "width",
+ {
+ get: function () {
+ return this._image.width / this._sheetColumns;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "height",
+ {
+ get: function () {
+ return this._image.height / this._sheetRows;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "center",
+ {
+ get: function () {
+ return this.size.divideBy(2);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "nrSheetElements",
+ {
+ get: function () {
+ return this._sheetRows * this._sheetColumns;
+ }
+ });
+
+SpriteSheet.prototype.draw = function (position, origin, sheetIndex, mirror) {
+ var columnIndex = sheetIndex % this._sheetColumns;
+ var rowIndex = Math.floor(sheetIndex / this._sheetColumns) % this._sheetRows;
+ var imagePart = new Rectangle(columnIndex * this.width, rowIndex * this.height,
+ this.width, this.height);
+ Canvas2D.drawImage(this._image, position, 0, 1, origin, imagePart, mirror);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/game-layout.css b/21_StoringRecallingGameData/PenguinPairs4/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Animal.js b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Animal.js
new file mode 100644
index 0000000..c4261a1
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Animal.js
@@ -0,0 +1,32 @@
+"use strict";
+
+function Animal(color, sprite, layer) {
+ SpriteGameObject.call(this, sprite, layer);
+
+ this.initialPosition = Vector2.zero;
+ this.boxed = (color === color.toUpperCase());
+ this.initialEmptyBox = this.boxed && color.toLowerCase() === "x";
+ this.sheetIndex = "brgyopmx".indexOf(color.toLowerCase());
+}
+
+Animal.prototype = Object.create(SpriteGameObject.prototype);
+
+Animal.prototype.reset = function () {
+ this.position = this.initialPosition.copy();
+ this.velocity = Vector2.zero;
+ this.visible = true;
+ if (this.initialEmptyBox)
+ this.sheetIndex = 7;
+};
+
+Animal.prototype.isSeal = function () {
+ return this.sheetIndex === 7 && !this.boxed;
+};
+
+Animal.prototype.isMulticolor = function () {
+ return this.sheetIndex === 6 && !this.boxed;
+};
+
+Animal.prototype.isEmptyBox = function () {
+ return this.sheetIndex === 7 && this.boxed;
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObject.js b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObject.js
new file mode 100644
index 0000000..9d3151e
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObject.js
@@ -0,0 +1,56 @@
+"use strict";
+
+function GameObject(layer, id) {
+ IGameLoopObject.call(this);
+
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+GameObject.prototype = Object.create(IGameLoopObject.prototype);
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
+
+GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObjectGrid.js b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObjectGrid.js
new file mode 100644
index 0000000..a25f664
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObjectGrid.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function GameObjectGrid(rows, columns, layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.cellWidth = 0;
+ this.cellHeight = 0;
+ this._rows = rows;
+ this._columns = columns;
+}
+
+GameObjectGrid.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(GameObjectGrid.prototype, "rows", {
+ get: function () {
+ return this._rows;
+ }
+});
+
+Object.defineProperty(GameObjectGrid.prototype, "columns", {
+ get: function () {
+ return this._columns;
+ }
+});
+
+GameObjectGrid.prototype.add = function (gameobject) {
+ var row = Math.floor(this._gameObjects.length / this._columns);
+ var col = this._gameObjects.length % this._columns;
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.addAt = function (gameobject, col, row) {
+ this._gameObjects[row * this._columns + col] = gameobject;
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.at = function (col, row) {
+ var index = row * this._columns + col;
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ else
+ return this._gameObjects[index];
+};
+
+GameObjectGrid.prototype.getAnchorPosition = function (gameobject) {
+ var l = this._gameObjects.length;
+ for (var i = 0; i < l; ++i)
+ if (this._gameObjects[i] == gameobject) {
+ var row = Math.floor(i / this.columns);
+ var col = i - row * this.columns;
+ return new Vector2(col * this.cellWidth, row * this.cellHeight);
+ }
+ return Vector2.zero;
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObjectList.js b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..b153446
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/GameObjectList.js
@@ -0,0 +1,79 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer, id) {
+ GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Level.js b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Level.js
new file mode 100644
index 0000000..efc8da9
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Level.js
@@ -0,0 +1,116 @@
+"use strict";
+
+function Level(levelIndex) {
+ GameObjectList.call(this);
+
+ var levelData = window.LEVELS[levelIndex];
+
+ this.animals = [];
+ this.sharks = [];
+ this.levelIndex = levelIndex;
+
+ this.add(new SpriteGameObject(sprites.background_level, ID.layer_background));
+
+ this.hintButton = new Button(sprites.button_hint, ID.layer_overlays);
+ this.hintButton.position = new Vector2(916, 20);
+ this.add(this.hintButton);
+
+ this.retryButton = new Button(sprites.button_retry, ID.layer_overlays);
+ this.retryButton.position = new Vector2(916, 20);
+ this.retryButton.visible = false;
+ this.add(this.retryButton);
+
+ this.quitButton = new Button(sprites.button_quit, ID.layer_overlays);
+ this.quitButton.position = new Vector2(1058, 20);
+ this.add(this.quitButton);
+
+ var width = levelData.tiles[0].length;
+ var height = levelData.tiles.length;
+
+ var playingField = new GameObjectList(ID.layer_objects);
+ playingField.position = new Vector2((Game.size.x - width * 73) / 2, 100);
+ this.add(playingField);
+
+ var tileField = new GameObjectGrid(height, width, ID.layer_objects, ID.tiles);
+ tileField.cellHeight = 72;
+ tileField.cellWidth = 73;
+ for (var row = 0; row < height; row++) {
+ for (var col = 0; col < width; col++) {
+ var t = null;
+
+ switch (levelData.tiles[row][col]) {
+ case '.' :
+ t = new Tile(sprites.field, ID.layer_objects);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ break;
+ case ' ':
+ t = new Tile(sprites.wall, ID.layer_objects);
+ t.type = TileType.background;
+ tileField.addAt(t, col, row);
+ break;
+ case 'r':
+ case 'b':
+ case 'g':
+ case 'o':
+ case 'p':
+ case 'y':
+ case 'm':
+ case 'x':
+ case 'R':
+ case 'B':
+ case 'G':
+ case 'O':
+ case 'P':
+ case 'Y':
+ case 'M':
+ case 'X':
+ t = new Tile(sprites.field, ID.layer_objects);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ var animalSprite = sprites.penguin;
+ if (levelData.tiles[row][col] === levelData.tiles[row][col].toUpperCase())
+ animalSprite = sprites.penguin_boxed;
+ var p = new Animal(levelData.tiles[row][col], animalSprite, ID.layer_objects_1);
+ p.position = t.position.copy();
+ p.initialPosition = t.position.copy();
+ playingField.add(p);
+ this.animals.push(p);
+ break;
+ case '@':
+ t = new Tile(sprites.field);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ var s = new SpriteGameObject(sprites.shark, ID.layer_objects_1);
+ s.position = t.position.copy();
+ playingField.add(s);
+ this.sharks.push(s);
+ break;
+ default :
+ t = new Tile(sprites.wall, ID.layer_objects);
+ tileField.addAt(t, col, row);
+ t.type = TileType.wall;
+ break;
+ }
+ }
+ }
+ playingField.add(tileField);
+
+ var helpField = new SpriteGameObject(sprites.help, ID.layer_overlays);
+ helpField.position = new Vector2(helpField.screenCenterX, 780);
+ this.add(helpField);
+ var helpLabel = new Label("Arial", "24px", ID.layer_overlays_1);
+ helpLabel.text = levelData.hint;
+ helpLabel.color = Color.darkBlue;
+ helpLabel.position = new Vector2(helpLabel.screenCenterX, 780 + (helpField.height - helpLabel.height) / 2);
+ this.add(helpLabel);
+}
+
+Level.prototype = Object.create(GameObjectList.prototype);
+
+Level.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.quitButton.pressed) {
+ GameStateManager.switchTo(ID.game_state_levelselect);
+ }
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gameobjects/SpriteGameObject.js b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..ad72ce1
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/SpriteGameObject.js
@@ -0,0 +1,86 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.sprite = sprite;
+
+ this.origin = Vector2.zero;
+ this.mirror = false;
+ this._sheetIndex = 0;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return this.sprite.size;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "center",
+ {
+ get: function () {
+ return this.sprite.center;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "sheetIndex",
+ {
+ get: function () {
+ return this._sheetIndex;
+ },
+ set: function (value) {
+ if (value >= 0)
+ this._sheetIndex = value % this.sprite.nrSheetElements;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "boundingBox",
+ {
+ get: function () {
+ var leftTop = this.worldPosition.subtractFrom((this.origin));
+ return new Rectangle(leftTop.x, leftTop.y, this.width, this.height);
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (this._visible)
+ this.sprite.draw(this.worldPosition, this.origin, this._sheetIndex, this.mirror);
+};
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Tile.js b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Tile.js
new file mode 100644
index 0000000..3b19fba
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gameobjects/Tile.js
@@ -0,0 +1,20 @@
+"use strict";
+
+var TileType = {
+ normal: 0,
+ background: 1,
+ wall: 2
+};
+
+function Tile(sprite, layer) {
+ SpriteGameObject.call(this, sprite, layer);
+ this.type = TileType.normal;
+}
+
+Tile.prototype = Object.create(SpriteGameObject.prototype);
+
+Tile.prototype.draw = function () {
+ if (this.type === TileType.background)
+ return;
+ SpriteGameObject.prototype.draw.call(this);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/geom/Rectangle.js b/21_StoringRecallingGameData/PenguinPairs4/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/geom/Vector2.js b/21_StoringRecallingGameData/PenguinPairs4/geom/Vector2.js
new file mode 100644
index 0000000..04bf6a8
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor == Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor == Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor == Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor == Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gui/Button.js b/21_StoringRecallingGameData/PenguinPairs4/gui/Button.js
new file mode 100644
index 0000000..d1d9d63
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gui/Button.js
@@ -0,0 +1,18 @@
+"use strict";
+
+function Button(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+
+ this.pressed = false;
+ this.down = false;
+}
+
+Button.prototype = Object.create(SpriteGameObject.prototype);
+
+Button.prototype.handleInput = function (delta) {
+ var boundingBox = this.boundingBox;
+ this.pressed = this.visible && (Touch.containsTouchPress(boundingBox) ||
+ Mouse.containsMousePress(boundingBox));
+ this.down = this.visible && (Touch.containsTouch(boundingBox) ||
+ Mouse.containsMouseDown(boundingBox));
+};
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gui/Label.js b/21_StoringRecallingGameData/PenguinPairs4/gui/Label.js
new file mode 100644
index 0000000..41c5af6
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gui/Label.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function calculateTextSize(fontname, fontsize, text) {
+ var div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = -1000;
+ div.style.top = -1000;
+ document.body.appendChild(div);
+ text = typeof text !== 'undefined' ? text : "M";
+ div.style.fontSize = "" + fontsize;
+ div.style.fontFamily = fontname;
+ div.innerHTML = text;
+ var size = new Vector2(div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+ return size;
+}
+
+function Label(fontname, fontsize, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.color = Color.black;
+ this.origin = Vector2.zero;
+ this._fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ this._fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+ this._contents = "";
+ this._align = "left";
+ this._size = Vector2.zero;
+}
+
+Label.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(Label.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "width",
+ {
+ get: function () {
+ return this._size.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "height",
+ {
+ get: function () {
+ return this._size.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(Label.prototype, "text",
+ {
+ get: function () {
+ return this._contents;
+ },
+
+ set: function (value) {
+ this._contents = value;
+ this._size = calculateTextSize(this._fontname, this._fontsize, value);
+ }
+ });
+
+Label.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawText(this._contents, this.worldPosition,
+ this.origin, this.color, this._align,
+ this._fontname, this._fontsize);
+};
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gui/LevelButton.js b/21_StoringRecallingGameData/PenguinPairs4/gui/LevelButton.js
new file mode 100644
index 0000000..4ba1fcb
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gui/LevelButton.js
@@ -0,0 +1,59 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new SpriteGameObject(sprites.level_solved, ID.layer_overlays);
+ this._levelUnsolved = new SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+
+ this.add(this._levelUnsolved);
+
+ this._levelLocked = new SpriteGameObject(sprites.level_locked, ID.layer_overlays_2);
+ this.add(this._levelLocked);
+
+ var textLabel = new Label("Arial", "20px", ID.layer_overlays_1);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new Vector2(this._levelSolved.width - textLabel.width,
+ this._levelSolved.height - textLabel.height + 50).divideBy(2);
+ textLabel.color = Color.black;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (window.LEVELS[this.levelIndex].locked)
+ return;
+ if (Touch.isTouchDevice)
+ this.pressed = this.visible && Touch.containsTouchPress(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(Mouse.position);
+};
+
+LevelButton.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ var currLevel = window.LEVELS[this.levelIndex];
+ this._levelLocked.visible = currLevel.locked;
+ this._levelSolved.visible = currLevel.solved;
+ this._levelUnsolved.visible = !currLevel.solved;
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gui/OnOffButton.js b/21_StoringRecallingGameData/PenguinPairs4/gui/OnOffButton.js
new file mode 100644
index 0000000..78d25bb
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gui/OnOffButton.js
@@ -0,0 +1,28 @@
+"use strict";
+
+function OnOffButton(sprite, layer) {
+ SpriteGameObject.call(this, sprite, layer);
+}
+
+OnOffButton.prototype = Object.create(SpriteGameObject.prototype);
+
+Object.defineProperty(OnOffButton.prototype, "on",
+ {
+ get: function () {
+ return this.sheetIndex === 1;
+ },
+ set: function (value) {
+ if (value)
+ this.sheetIndex = 1;
+ else
+ this.sheetIndex = 0;
+ }
+ });
+
+OnOffButton.prototype.handleInput = function (delta) {
+ if (!this.visible)
+ return;
+ if (Touch.containsTouchPress(this.boundingBox) ||
+ Mouse.containsMousePress(this.boundingBox))
+ this.sheetIndex = (this.sheetIndex + 1) % 2;
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/gui/Slider.js b/21_StoringRecallingGameData/PenguinPairs4/gui/Slider.js
new file mode 100644
index 0000000..20dca1f
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/gui/Slider.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function Slider(sliderback, sliderfront, layer) {
+ GameObjectList.call(this, layer);
+ this.dragging = false;
+ this.draggingId = -1;
+ this.leftmargin = 5;
+ this.rightmargin = 7;
+
+ this.back = new SpriteGameObject(sliderback);
+ this.front = new SpriteGameObject(sliderfront, 1);
+ this.front.position = new Vector2(this.leftmargin, 8);
+ this.add(this.back);
+ this.add(this.front);
+}
+
+Slider.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(Slider.prototype, "value",
+ {
+ get: function () {
+ return (this.front.position.x - this.back.position.x - this.leftmargin) /
+ (this.back.width - this.front.width - this.leftmargin - this.rightmargin);
+ },
+ set: function (value) {
+ var newxpos = value * (this.back.width - this.front.width - this.leftmargin - this.rightmargin)
+ + this.back.position.x + this.leftmargin;
+ this.front.position = new Vector2(newxpos, this.front.position.y);
+ }
+ });
+
+Slider.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (Mouse.left.down || Touch.isTouching) {
+ if (Touch.isTouchDevice) {
+ if (Touch.containsTouch(this.back.boundingBox))
+ this.draggingId = Touch.getIndexInRect((this.back.boundingBox));
+ if (Touch.containsTouch(this.back.boundingBox) || this.dragging) {
+ var touchPos = Touch.getPosition(this.draggingId);
+ this.front.position = new Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(touchPos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ this.dragging = true;
+ }
+ } else {
+ var mousePos = Mouse.position;
+ if (this.back.boundingBox.contains(mousePos) || this.dragging) {
+ this.front.position = new Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(mousePos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ this.dragging = true;
+ }
+ }
+ }
+ else {
+ this.dragging = false;
+ this.draggingId = -1;
+ }
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/input/ButtonState.js b/21_StoringRecallingGameData/PenguinPairs4/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/input/Keyboard.js b/21_StoringRecallingGameData/PenguinPairs4/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/21_StoringRecallingGameData/PenguinPairs4/input/Mouse.js b/21_StoringRecallingGameData/PenguinPairs4/input/Mouse.js
new file mode 100644
index 0000000..662f762
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/input/Touch.js b/21_StoringRecallingGameData/PenguinPairs4/input/Touch.js
new file mode 100644
index 0000000..7adb4ce
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/input/Touch.js
@@ -0,0 +1,108 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return Vector2.zero;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/states/HelpState.js b/21_StoringRecallingGameData/PenguinPairs4/states/HelpState.js
new file mode 100644
index 0000000..60f3ca6
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/states/HelpState.js
@@ -0,0 +1,22 @@
+"use strict";
+
+function HelpState(layer) {
+ GameObjectList.call(this, layer);
+
+ // the background
+ var background = new SpriteGameObject(sprites.background_help);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new Button(sprites.button_back, 100);
+ this.backButton.position = new Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+HelpState.prototype = Object.create(GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/states/LevelMenuState.js b/21_StoringRecallingGameData/PenguinPairs4/states/LevelMenuState.js
new file mode 100644
index 0000000..f8d2837
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/states/LevelMenuState.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ this.background = new SpriteGameObject(sprites.background_levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0; i < window.LEVELS.length; i += 1) {
+ var row = Math.floor(i / 5);
+ var column = i % 5;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new Vector2(column * (level.width + 30) + 155,
+ row * (level.height + 5) + 230);
+ //console.log("Level " + i + " at position " + level.position);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i += 1) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ var playingState = GameStateManager.get(ID.game_state_playing);
+ playingState.goToLevel(selectedLevel);
+ GameStateManager.switchTo(ID.game_state_playing);
+ }
+ else if (this.back.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/states/OptionsMenuState.js b/21_StoringRecallingGameData/PenguinPairs4/states/OptionsMenuState.js
new file mode 100644
index 0000000..f4ea732
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/states/OptionsMenuState.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function OptionsMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ var background = new SpriteGameObject(sprites.background_options, ID.layer_background);
+ this.add(background);
+
+ var onOffLabel = new Label("Arial", "60px", ID.layer_overlays);
+ onOffLabel.text = "Hints";
+ onOffLabel.position = new Vector2(150, 360);
+ onOffLabel.color = Color.darkBlue;
+ this.add(onOffLabel);
+
+ this.onOffButton = new OnOffButton(sprites.button_offon, ID.layer_overlays);
+ this.onOffButton.position = new Vector2(650, 340);
+ this.onOffButton.on = GameSettings.hints;
+ this.add(this.onOffButton);
+
+ var musicText = new Label("Arial", "60px", ID.layer_overlays);
+ musicText.text = "Music volume";
+ musicText.position = new Vector2(150, 490);
+ musicText.color = Color.darkBlue;
+ this.add(musicText);
+
+ this.musicSlider = new Slider(sprites.slider_bar, sprites.slider_button, ID.layer_overlays);
+ this.musicSlider.position = new Vector2(650, 500);
+ this.musicSlider.value = sounds.music.volume;
+ this.add(this.musicSlider);
+
+ this.backButton = new Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+OptionsMenuState.prototype = Object.create(GameObjectList.prototype);
+
+OptionsMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
+
+OptionsMenuState.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ sounds.music.volume = this.musicSlider.value;
+ GameSettings.hints = this.onOffButton.on;
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/states/PlayingState.js b/21_StoringRecallingGameData/PenguinPairs4/states/PlayingState.js
new file mode 100644
index 0000000..22d0b33
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/states/PlayingState.js
@@ -0,0 +1,58 @@
+"use strict";
+
+function PlayingState() {
+ IGameLoopObject.call(this);
+
+ this.currentLevelIndex = -1;
+ this.levels = [];
+
+ this.loadLevelsStatus();
+ this.loadLevels();
+}
+
+PlayingState.prototype = Object.create(IGameLoopObject.prototype);
+
+Object.defineProperty(PlayingState.prototype, "currentLevel", {
+ get: function () {
+ return this.levels[this.currentLevelIndex];
+ }
+});
+
+PlayingState.prototype.handleInput = function (delta) {
+ this.currentLevel.handleInput(delta);
+};
+
+PlayingState.prototype.update = function (delta) {
+ this.currentLevel.update(delta);
+};
+
+PlayingState.prototype.draw = function () {
+ this.currentLevel.draw();
+};
+
+PlayingState.prototype.reset = function () {
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.goToLevel = function (levelIndex) {
+ if (levelIndex < 0 || levelIndex >= window.LEVELS.length)
+ return;
+ this.currentLevelIndex = levelIndex;
+};
+
+PlayingState.prototype.loadLevels = function () {
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel++)
+ this.levels.push(new Level(currLevel));
+};
+
+PlayingState.prototype.loadLevelsStatus = function () {
+ if (localStorage && localStorage.penguinPairsLevels) {
+ window.LEVELS = JSON.parse(localStorage.penguinPairsLevels);
+ }
+};
+
+PlayingState.prototype.writeLevelsStatus = function () {
+ if (!localStorage)
+ return;
+ localStorage.penguinPairsLevels = JSON.stringify(window.LEVELS);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/states/TitleMenuState.js b/21_StoringRecallingGameData/PenguinPairs4/states/TitleMenuState.js
new file mode 100644
index 0000000..a4eee1b
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/states/TitleMenuState.js
@@ -0,0 +1,32 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ var background = new SpriteGameObject(sprites.background_title, ID.layer_background);
+ this.add(background);
+
+ this.playButton = new Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new Vector2(415, 540);
+ this.add(this.playButton);
+
+ this.optionsButton = new Button(sprites.button_options, ID.layer_overlays);
+ this.optionsButton.position = new Vector2(415, 650);
+ this.add(this.optionsButton);
+
+ this.helpButton = new Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new Vector2(415, 760);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ GameStateManager.switchTo(ID.game_state_help);
+ else if (this.optionsButton.pressed)
+ GameStateManager.switchTo(ID.game_state_options);
+};
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/system/Color.js b/21_StoringRecallingGameData/PenguinPairs4/system/Color.js
new file mode 100644
index 0000000..19ee82a
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+ var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+ };
\ No newline at end of file
diff --git a/21_StoringRecallingGameData/PenguinPairs4/system/Keys.js b/21_StoringRecallingGameData/PenguinPairs4/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/21_StoringRecallingGameData/PenguinPairs4/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/LAB.min.js b/22_PairingPenguins/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/22_PairingPenguins/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/Canvas2D.js b/22_PairingPenguins/PenguinPairs5/Canvas2D.js
new file mode 100644
index 0000000..8df997b
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/Canvas2D.js
@@ -0,0 +1,149 @@
+"use strict";
+
+function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._pixeldrawingCanvas = null;
+ this._canvasOffset = Vector2.zero;
+}
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new Vector2(this._canvas.width / Game.size.x,
+ this._canvas.height / Game.size.y);
+ }
+ });
+
+Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+
+ this._pixeldrawingCanvas = document.createElement('canvas');
+
+ window.onresize = this.resize;
+ this.resize();
+};
+
+Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+};
+
+Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = Canvas2D._canvas;
+ var gameArea = Canvas2D._div;
+ var widthToHeight = Game.size.x / Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ Canvas2D._canvasOffset = offset;
+};
+
+Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect, mirror) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ if (mirror) {
+ this._canvasContext.scale(scale * canvasScale.x * -1, scale * canvasScale.y);
+ this._canvasContext.translate(-position.x - sourceRect.width, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ sourceRect.width - origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ else {
+ this._canvasContext.scale(scale * canvasScale.x, scale * canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : Vector2.zero;
+ color = typeof color !== 'undefined' ? color : Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawPixel = function (x, y, color) {
+ var canvasscale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasscale.x, canvasscale.y);
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.fillRect(x, y, 1, 1);
+ this._canvasContext.restore();
+};
+
+Canvas2D_Singleton.prototype.drawRectangle = function (x, y, width, height) {
+ var canvasScale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.strokeRect(x, y, width, height);
+ this._canvasContext.restore();
+};
+
+var Canvas2D = new Canvas2D_Singleton();
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/Game.js b/22_PairingPenguins/PenguinPairs5/Game.js
new file mode 100644
index 0000000..65f776e
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/Game.js
@@ -0,0 +1,85 @@
+"use strict";
+
+var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+}
+
+Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new Vector2(x, y);
+
+ Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+};
+
+Game_Singleton.prototype.initialize = function () {
+};
+
+Game_Singleton.prototype.loadAssets = function () {
+};
+
+Game_Singleton.prototype.assetLoadingLoop = function () {
+ Canvas2D.clear();
+ Canvas2D.drawText(Math.round((Game._totalSprites - Game._spritesStillLoading) /
+ Game._totalSprites * 100) + "%");
+
+ if (Game._spritesStillLoading > 0)
+ requestAnimationFrame(Game.assetLoadingLoop);
+ else {
+ Game.initialize();
+ requestAnimationFrame(Game.mainLoop);
+ }
+};
+
+Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ Game._totalTime += delta;
+
+ GameStateManager.handleInput(delta);
+ GameStateManager.update(delta);
+ Canvas2D.clear();
+ GameStateManager.draw();
+
+ Keyboard.reset();
+ Mouse.reset();
+ Touch.reset();
+
+ requestAnimationFrame(Game.mainLoop);
+};
+
+var Game = new Game_Singleton();
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/GameStateManager.js b/22_PairingPenguins/PenguinPairs5/GameStateManager.js
new file mode 100644
index 0000000..df07f76
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/GameStateManager.js
@@ -0,0 +1,47 @@
+"use strict";
+
+function GameStateManager_Singleton() {
+ this._gameStates = [];
+ this._currentGameState = null;
+}
+
+GameStateManager_Singleton.prototype.add = function (gamestate) {
+ this._gameStates.push(gamestate);
+ this._currentGameState = gamestate;
+ return this._gameStates.length - 1;
+};
+
+GameStateManager_Singleton.prototype.get = function (id) {
+ if (id < 0 || id >= this._gameStates.length)
+ return null;
+ else
+ return this._gameStates[id];
+};
+
+GameStateManager_Singleton.prototype.switchTo = function (id) {
+ if (id < 0 || id >= this._gameStates.length)
+ return;
+ this._currentGameState = this._gameStates[id];
+};
+
+GameStateManager_Singleton.prototype.handleInput = function (delta) {
+ if (this._currentGameState != null)
+ this._currentGameState.handleInput(delta);
+};
+
+GameStateManager_Singleton.prototype.update = function (delta) {
+ if (this._currentGameState != null)
+ this._currentGameState.update(delta);
+};
+
+GameStateManager_Singleton.prototype.draw = function () {
+ if (this._currentGameState != null)
+ this._currentGameState.draw();
+};
+
+GameStateManager_Singleton.prototype.reset = function () {
+ if (this._currentGameState != null)
+ this._currentGameState.reset();
+};
+
+var GameStateManager = new GameStateManager_Singleton();
diff --git a/22_PairingPenguins/PenguinPairs5/IGameLoopObject.js b/22_PairingPenguins/PenguinPairs5/IGameLoopObject.js
new file mode 100644
index 0000000..908b40d
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/IGameLoopObject.js
@@ -0,0 +1,19 @@
+"use strict";
+
+function IGameLoopObject() {
+}
+
+IGameLoopObject.prototype.initialize = function () {
+};
+
+IGameLoopObject.prototype.handleInput = function (delta) {
+};
+
+IGameLoopObject.prototype.update = function (delta) {
+};
+
+IGameLoopObject.prototype.draw = function () {
+};
+
+IGameLoopObject.prototype.reset = function () {
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/LAB.min.js b/22_PairingPenguins/PenguinPairs5/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/Math.js b/22_PairingPenguins/PenguinPairs5/Math.js
new file mode 100644
index 0000000..5e1da3b
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/Math.js
@@ -0,0 +1,23 @@
+"use strict";
+
+if (!Math.sign) {
+ Math.sign = function (value) {
+ if (value > 0)
+ return 1;
+ else if (value < 0)
+ return -1;
+ else
+ return 0;
+ };
+}
+
+if (!Math.clamp) {
+ Math.clamp = function (value, min, max) {
+ if (value < min)
+ return min;
+ else if (value > max)
+ return max;
+ else
+ return value;
+ };
+}
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/PenguinPairs.html b/22_PairingPenguins/PenguinPairs5/PenguinPairs.html
new file mode 100644
index 0000000..a02c579
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/PenguinPairs.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+ Penguin Pairs
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/PenguinPairs.js b/22_PairingPenguins/PenguinPairs5/PenguinPairs.js
new file mode 100644
index 0000000..f94035d
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/PenguinPairs.js
@@ -0,0 +1,94 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+var GameSettings = {
+ hints: true
+};
+
+Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return new SpriteSheet("../../assets/PenguinPairs/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new Sound("../../assets/PenguinPairs/sounds/" + sound, looping);
+ };
+
+ sprites.arrow = loadSprite("spr_arrow@4.png");
+ sprites.arrow_hover = loadSprite("spr_arrow_hover@4.png");
+ sprites.arrow_hint = loadSprite("spr_arrow_hint@4.png");
+ sprites.background_title = loadSprite("spr_background_title.jpg");
+ sprites.background_help = loadSprite("spr_background_help.jpg");
+ sprites.background_level = loadSprite("spr_background_level.jpg");
+ sprites.background_levelselect = loadSprite("spr_background_levelselect.jpg");
+ sprites.background_options = loadSprite("spr_background_options.jpg");
+ sprites.button_back = loadSprite("spr_button_back.jpg");
+ sprites.button_help = loadSprite("spr_button_help.jpg");
+ sprites.button_hint = loadSprite("spr_button_hint.png");
+ sprites.button_offon = loadSprite("spr_button_offon@2.png");
+ sprites.button_options = loadSprite("spr_button_options.jpg");
+ sprites.button_play = loadSprite("spr_button_play.jpg");
+ sprites.button_quit = loadSprite("spr_button_quit.png");
+ sprites.button_retry = loadSprite("spr_button_retry.png");
+ sprites.field = loadSprite("spr_field@2.png");
+ sprites.frame_goal = loadSprite("spr_frame_goal.jpg");
+ sprites.help = loadSprite("spr_help.jpg");
+ if (Touch.isTouchDevice)
+ sprites.level_finished = loadSprite("spr_level_finished_tap.png");
+ else
+ sprites.level_finished = loadSprite("spr_level_finished_click.png");
+ sprites.level_unsolved = loadSprite("spr_level_unsolved.png");
+ sprites.level_solved = loadSprite("spr_level_solved@6.png");
+ sprites.level_locked = loadSprite("spr_lock.png");
+ sprites.penguin = loadSprite("spr_penguin@8.png");
+ sprites.penguin_boxed = loadSprite("spr_penguin_boxed@8.png");
+ sprites.penguin_empty = loadSprite("spr_penguin_empty.png");
+ sprites.penguin_pairs = loadSprite("spr_penguin_pairs@8.png");
+ sprites.shark = loadSprite("spr_shark.png");
+ sprites.slider_bar = loadSprite("spr_slider_bar.jpg");
+ sprites.slider_button = loadSprite("spr_slider_button.jpg");
+ sprites.wall = loadSprite("spr_wall.png");
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.eat = loadSound("snd_eat");
+ sounds.lost = loadSound("snd_lost");
+ sounds.won = loadSound("snd_won");
+ sounds.pair = loadSound("snd_pair");
+};
+
+Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.tiles = 1;
+ ID.pairList = 2;
+ ID.animalSelector = 3;
+
+ // create the different game modes
+ ID.game_state_title = GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = GameStateManager.add(new HelpState());
+ ID.game_state_options = GameStateManager.add(new OptionsMenuState());
+ ID.game_state_playing = GameStateManager.add(new PlayingState());
+ ID.game_state_levelselect = GameStateManager.add(new LevelMenuState());
+
+ // set the current game mode
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/Sound.js b/22_PairingPenguins/PenguinPairs5/Sound.js
new file mode 100644
index 0000000..5d9671b
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/Sound.js
@@ -0,0 +1,35 @@
+"use strict";
+
+function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+}
+
+Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function (value) {
+ this.snd.volume = value;
+ }
+ });
+
+Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function () {
+ this.load();
+ this.autoplay = true;
+ }, false);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/SpriteSheet.js b/22_PairingPenguins/PenguinPairs5/SpriteSheet.js
new file mode 100644
index 0000000..7486243
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/SpriteSheet.js
@@ -0,0 +1,79 @@
+"use strict";
+
+function SpriteSheet(imageName) {
+ console.log("Loading sprite: " + imageName);
+ Game._spritesStillLoading += 1;
+ Game._totalSprites += 1;
+
+ this._image = new Image();
+ this._image.src = imageName;
+ this._sheetColumns = 1;
+ this._sheetRows = 1;
+ this._collisionMask = null;
+
+ var sprite = this;
+ this._image.onload = function () {
+ Game._spritesStillLoading -= 1;
+ };
+
+ // determine the number of sheet rows and columns
+ var pathSplit = imageName.split('/');
+ var fileName = pathSplit[pathSplit.length - 1];
+ var fileSplit = fileName.split("/")[0].split(".")[0].split("@");
+ if (fileSplit.length <= 1)
+ return;
+ var colRow = fileSplit[fileSplit.length - 1].split("x");
+ this._sheetColumns = colRow[0];
+ if (colRow.length === 2)
+ this._sheetRows = colRow[1];
+}
+
+Object.defineProperty(SpriteSheet.prototype, "image",
+ {
+ get: function () {
+ return this._image;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "width",
+ {
+ get: function () {
+ return this._image.width / this._sheetColumns;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "height",
+ {
+ get: function () {
+ return this._image.height / this._sheetRows;
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "center",
+ {
+ get: function () {
+ return this.size.divideBy(2);
+ }
+ });
+
+Object.defineProperty(SpriteSheet.prototype, "nrSheetElements",
+ {
+ get: function () {
+ return this._sheetRows * this._sheetColumns;
+ }
+ });
+
+SpriteSheet.prototype.draw = function (position, origin, sheetIndex, mirror) {
+ var columnIndex = sheetIndex % this._sheetColumns;
+ var rowIndex = Math.floor(sheetIndex / this._sheetColumns) % this._sheetRows;
+ var imagePart = new Rectangle(columnIndex * this.width, rowIndex * this.height,
+ this.width, this.height);
+ Canvas2D.drawImage(this._image, position, 0, 1, origin, imagePart, mirror);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/game-layout.css b/22_PairingPenguins/PenguinPairs5/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/Animal.js b/22_PairingPenguins/PenguinPairs5/gameobjects/Animal.js
new file mode 100644
index 0000000..1dc8a7e
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/Animal.js
@@ -0,0 +1,117 @@
+"use strict";
+
+function Animal(color, sprite, layer) {
+ SpriteGameObject.call(this, sprite, layer);
+
+ this.initialPosition = Vector2.zero;
+ this.boxed = (color === color.toUpperCase());
+ this.initialEmptyBox = this.boxed && color.toLowerCase() === "x";
+ this.sheetIndex = "brgyopmx".indexOf(color.toLowerCase());
+}
+
+Animal.prototype = Object.create(SpriteGameObject.prototype);
+
+Object.defineProperty(Animal.prototype, "currentBlock",
+ {
+ get: function () {
+ var tileField = this.root.find(ID.tiles);
+ var p = new Vector2(Math.floor(this.position.x / tileField.cellWidth),
+ Math.floor(this.position.y / tileField.cellHeight));
+ if (this.velocity.x > 0)
+ p.x++;
+ if (this.velocity.y > 0)
+ p.y++;
+ return p;
+ }
+ });
+
+Animal.prototype.reset = function () {
+ this.position = this.initialPosition.copy();
+ this.velocity = Vector2.zero;
+ this.visible = true;
+ if (this.initialEmptyBox)
+ this.sheetIndex = 7;
+};
+
+Animal.prototype.handleInput = function (delta) {
+ if (!this.visible || this.boxed || !this.velocity.isZero)
+ return;
+ if (Touch.isTouchDevice) {
+ if (!Touch.containsTouchPress(this.boundingBox))
+ return;
+ } else {
+ if (!Mouse.left.pressed || !this.boundingBox.contains(Mouse.position))
+ return;
+ }
+ var animalSelector = this.root.find(ID.animalSelector);
+ if (animalSelector.visible)
+ return;
+
+ animalSelector.position = this.position;
+ animalSelector.visible = true;
+ animalSelector.selectedAnimal = this;
+ Mouse.reset();
+ Touch.reset();
+};
+
+Animal.prototype.update = function (delta) {
+ SpriteGameObject.prototype.update.call(this, delta);
+ if (!this.visible || this.velocity.isZero)
+ return;
+
+ var target = this.currentBlock;
+ var tileField = this.root.find(ID.tiles);
+
+ if (tileField.getTileType(target) === TileType.background) {
+ this.visible = false;
+ this.velocity = Vector2.zero;
+ }
+ else if (tileField.getTileType(target) === TileType.wall)
+ this.stopMoving();
+ else {
+ var s = this.root.findSharkAtPosition(target);
+ if (s !== null && s.visible) {
+ s.visible = false;
+ this.visible = false;
+ this.stopMoving();
+ }
+
+ var a = this.root.findAnimalAtPosition(target);
+ if (a === null || !a.visible)
+ return;
+ if (a.isSeal())
+ this.stopMoving();
+ else if (a.isEmptyBox()) {
+ this.visible = false;
+ a.sheetIndex = this.sheetIndex;
+ }
+ else if (a.sheetIndex === this.sheetIndex || this.isMulticolor() || a.isMulticolor()) {
+ a.visible = false;
+ this.visible = false;
+ this.root.find(ID.pairList).addPair(this.sheetIndex);
+ }
+ else
+ this.stopMoving();
+ }
+};
+
+Animal.prototype.stopMoving = function () {
+ var tileField = this.root.find(ID.tiles);
+ this.velocity.normalize();
+ var currBlock = this.currentBlock;
+ this.position = new Vector2(Math.floor(currBlock.x - this.velocity.x) * tileField.cellWidth,
+ Math.floor(currBlock.y - this.velocity.y) * tileField.cellHeight);
+ this.velocity = Vector2.zero;
+};
+
+Animal.prototype.isSeal = function () {
+ return this.sheetIndex === 7 && !this.boxed;
+};
+
+Animal.prototype.isMulticolor = function () {
+ return this.sheetIndex === 6 && !this.boxed;
+};
+
+Animal.prototype.isEmptyBox = function () {
+ return this.sheetIndex === 7 && this.boxed;
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/AnimalSelector.js b/22_PairingPenguins/PenguinPairs5/gameobjects/AnimalSelector.js
new file mode 100644
index 0000000..02831dd
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/AnimalSelector.js
@@ -0,0 +1,44 @@
+"use strict";
+
+function AnimalSelector(layer, id) {
+ GameObjectList.call(this, layer, id);
+ this._arrowright = new Arrow(0);
+ this._arrowright.position = new Vector2(this._arrowright.width, 0);
+ this.add(this._arrowright);
+ this._arrowup = new Arrow(1);
+ this._arrowup.position = new Vector2(0, -this._arrowright.height);
+ this.add(this._arrowup);
+ this._arrowleft = new Arrow(2);
+ this._arrowleft.position = new Vector2(-this._arrowright.width, 0);
+ this.add(this._arrowleft);
+ this._arrowdown = new Arrow(3);
+ this._arrowdown.position = new Vector2(0, this._arrowright.height);
+ this.add(this._arrowdown);
+ this.selectedAnimal = null;
+ this.visible = false;
+}
+
+AnimalSelector.prototype = Object.create(GameObjectList.prototype);
+
+AnimalSelector.prototype.handleInput = function (delta) {
+ if (!this.visible)
+ return;
+ GameObjectList.prototype.handleInput.call(this, delta);
+
+ var animalVelocity = Vector2.zero;
+ if (this._arrowdown.pressed)
+ animalVelocity.y = 1;
+ else if (this._arrowup.pressed)
+ animalVelocity.y = -1;
+ else if (this._arrowleft.pressed)
+ animalVelocity.x = -1;
+ else if (this._arrowright.pressed)
+ animalVelocity.x = 1;
+ animalVelocity.multiplyWith(300);
+ if (Mouse.left.pressed || Touch.containsTouchPress(Game.screenRect))
+ this.visible = false;
+
+ if (this.selectedAnimal === null || animalVelocity.isZero)
+ return;
+ this.selectedAnimal.velocity = animalVelocity;
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/Arrow.js b/22_PairingPenguins/PenguinPairs5/gameobjects/Arrow.js
new file mode 100644
index 0000000..7e55c6d
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/Arrow.js
@@ -0,0 +1,22 @@
+"use strict";
+
+function Arrow(sheetIndex, layer, id) {
+ Button.call(this, sprites.arrow, layer, id);
+ this.sheetIndex = sheetIndex;
+ this.arrowHover = new SpriteGameObject(sprites.arrow_hover);
+ this.arrowHover.sheetIndex = sheetIndex;
+ this.arrowHover.visible = false;
+ this.arrowHover.parent = this;
+}
+
+Arrow.prototype = Object.create(Button.prototype);
+
+Arrow.prototype.handleInput = function (delta) {
+ Button.prototype.handleInput.call(this, delta);
+ this.arrowHover.visible = !Touch.isTouchDevice && this.boundingBox.contains(Mouse.position);
+};
+
+Arrow.prototype.draw = function () {
+ Button.prototype.draw.call(this);
+ this.arrowHover.draw();
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/GameObject.js b/22_PairingPenguins/PenguinPairs5/gameobjects/GameObject.js
new file mode 100644
index 0000000..9d3151e
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/GameObject.js
@@ -0,0 +1,56 @@
+"use strict";
+
+function GameObject(layer, id) {
+ IGameLoopObject.call(this);
+
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = Vector2.zero;
+ this.velocity = Vector2.zero;
+ this._visible = true;
+}
+
+GameObject.prototype = Object.create(IGameLoopObject.prototype);
+
+Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+GameObject.prototype.reset = function () {
+ this._visible = true;
+};
+
+GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/GameObjectGrid.js b/22_PairingPenguins/PenguinPairs5/gameobjects/GameObjectGrid.js
new file mode 100644
index 0000000..a25f664
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/GameObjectGrid.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function GameObjectGrid(rows, columns, layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.cellWidth = 0;
+ this.cellHeight = 0;
+ this._rows = rows;
+ this._columns = columns;
+}
+
+GameObjectGrid.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(GameObjectGrid.prototype, "rows", {
+ get: function () {
+ return this._rows;
+ }
+});
+
+Object.defineProperty(GameObjectGrid.prototype, "columns", {
+ get: function () {
+ return this._columns;
+ }
+});
+
+GameObjectGrid.prototype.add = function (gameobject) {
+ var row = Math.floor(this._gameObjects.length / this._columns);
+ var col = this._gameObjects.length % this._columns;
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.addAt = function (gameobject, col, row) {
+ this._gameObjects[row * this._columns + col] = gameobject;
+ gameobject.parent = this;
+ gameobject.position = new Vector2(col * this.cellWidth, row * this.cellHeight);
+};
+
+GameObjectGrid.prototype.at = function (col, row) {
+ var index = row * this._columns + col;
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ else
+ return this._gameObjects[index];
+};
+
+GameObjectGrid.prototype.getAnchorPosition = function (gameobject) {
+ var l = this._gameObjects.length;
+ for (var i = 0; i < l; ++i)
+ if (this._gameObjects[i] == gameobject) {
+ var row = Math.floor(i / this.columns);
+ var col = i - row * this.columns;
+ return new Vector2(col * this.cellWidth, row * this.cellHeight);
+ }
+ return Vector2.zero;
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/GameObjectList.js b/22_PairingPenguins/PenguinPairs5/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..b153446
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/GameObjectList.js
@@ -0,0 +1,79 @@
+"use strict";
+
+GameObjectList.prototype = Object.create(GameObject.prototype);
+
+function GameObjectList(layer, id) {
+ GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+}
+
+Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+});
+
+GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+};
+
+GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+};
+
+GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+};
+
+GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+};
+
+GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+};
+
+GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+};
+
+GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+};
+
+GameObjectList.prototype.draw = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+};
+
+GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/Level.js b/22_PairingPenguins/PenguinPairs5/gameobjects/Level.js
new file mode 100644
index 0000000..73ca34c
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/Level.js
@@ -0,0 +1,141 @@
+"use strict";
+
+function Level(levelIndex) {
+ GameObjectList.call(this);
+
+ var levelData = window.LEVELS[levelIndex];
+
+ this.animals = [];
+ this.sharks = [];
+ this.levelIndex = levelIndex;
+ this.firstMoveMade = false;
+
+ this.add(new SpriteGameObject(sprites.background_level, ID.layer_background));
+
+ this.hintButton = new Button(sprites.button_hint, ID.layer_overlays);
+ this.hintButton.position = new Vector2(916, 20);
+ this.add(this.hintButton);
+
+ this.retryButton = new Button(sprites.button_retry, ID.layer_overlays);
+ this.retryButton.position = new Vector2(916, 20);
+ this.retryButton.visible = false;
+ this.add(this.retryButton);
+
+ this.quitButton = new Button(sprites.button_quit, ID.layer_overlays);
+ this.quitButton.position = new Vector2(1058, 20);
+ this.add(this.quitButton);
+
+ var width = levelData.tiles[0].length;
+ var height = levelData.tiles.length;
+
+ var playingField = new GameObjectList(ID.layer_objects);
+ playingField.position = new Vector2((Game.size.x - width * 73) / 2, 100);
+ this.add(playingField);
+
+ var tileField = new TileField(height, width, ID.layer_objects, ID.tiles);
+ tileField.cellHeight = 72;
+ tileField.cellWidth = 73;
+ for (var row = 0; row < height; row++) {
+ for (var col = 0; col < width; col++) {
+ var t = null;
+
+ switch (levelData.tiles[row][col]) {
+ case '.' :
+ t = new Tile(sprites.field, ID.layer_objects);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ break;
+ case ' ':
+ t = new Tile(sprites.wall, ID.layer_objects);
+ t.type = TileType.background;
+ tileField.addAt(t, col, row);
+ break;
+ case 'r':
+ case 'b':
+ case 'g':
+ case 'o':
+ case 'p':
+ case 'y':
+ case 'm':
+ case 'x':
+ case 'R':
+ case 'B':
+ case 'G':
+ case 'O':
+ case 'P':
+ case 'Y':
+ case 'M':
+ case 'X':
+ t = new Tile(sprites.field, ID.layer_objects);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ var animalSprite = sprites.penguin;
+ if (levelData.tiles[row][col] === levelData.tiles[row][col].toUpperCase())
+ animalSprite = sprites.penguin_boxed;
+ var p = new Animal(levelData.tiles[row][col], animalSprite, ID.layer_objects_1);
+ p.position = t.position.copy();
+ p.initialPosition = t.position.copy();
+ playingField.add(p);
+ this.animals.push(p);
+ break;
+ case '@':
+ t = new Tile(sprites.field);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ var s = new SpriteGameObject(sprites.shark, ID.layer_objects_1);
+ s.position = t.position.copy();
+ playingField.add(s);
+ this.sharks.push(s);
+ break;
+ default :
+ t = new Tile(sprites.wall, ID.layer_objects);
+ tileField.addAt(t, col, row);
+ t.type = TileType.wall;
+ break;
+ }
+ }
+ }
+ playingField.add(tileField);
+
+ playingField.add(new AnimalSelector(ID.layer_objects_2, ID.animalSelector));
+
+ var pairList = new PairList(levelData.nrPairs, ID.layer_overlays, ID.pairList);
+ pairList.position = new Vector2(20, 15);
+ this.add(pairList);
+
+ var helpField = new SpriteGameObject(sprites.help, ID.layer_overlays);
+ helpField.position = new Vector2(helpField.screenCenterX, 780);
+ this.add(helpField);
+ var helpLabel = new Label("Arial", "24px", ID.layer_overlays_1);
+ helpLabel.text = levelData.hint;
+ helpLabel.color = Color.darkBlue;
+ helpLabel.position = new Vector2(helpLabel.screenCenterX, 780 + (helpField.height - helpLabel.height) / 2);
+ this.add(helpLabel);
+}
+
+Level.prototype = Object.create(GameObjectList.prototype);
+
+Level.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.retryButton.pressed)
+ this.reset();
+ else if (this.quitButton.pressed) {
+ GameStateManager.switchTo(ID.game_state_levelselect);
+ }
+};
+
+Level.prototype.findAnimalAtPosition = function (pos) {
+ for (var i = 0, j = this.animals.length; i < j; ++i)
+ if (this.animals[i].currentBlock.equals(pos) && this.animals[i].velocity.isZero)
+ return this.animals[i];
+ return null;
+};
+
+Level.prototype.findSharkAtPosition = function (pos) {
+ var tileField = this.find(ID.tiles);
+ var sharkPos = new Vector2(pos.x * tileField.cellWidth, pos.y * tileField.cellHeight);
+ for (var i = 0, l = this.sharks.length; i < l; ++i)
+ if (this.sharks[i].position.equals(sharkPos))
+ return this.sharks[i];
+ return null;
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/PairList.js b/22_PairingPenguins/PenguinPairs5/gameobjects/PairList.js
new file mode 100644
index 0000000..262280c
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/PairList.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function PairList(nrPairs, layer, id) {
+ SpriteGameObject.call(this, sprites.frame_goal, layer, id);
+ this._pairs = [];
+ for (var i = 0; i < nrPairs; i++)
+ this._pairs.push(7);
+ this._pairSprite = new SpriteGameObject(sprites.penguin_pairs);
+ this._pairSprite.parent = this;
+}
+PairList.prototype = Object.create(SpriteGameObject.prototype);
+
+Object.defineProperty(PairList.prototype, "completed",
+ {
+ get: function () {
+ for (var i = 0, l = this._pairs.length; i < l; i++)
+ if (this._pairs[i] === 7)
+ return false;
+ return true;
+ }
+ });
+
+PairList.prototype.addPair = function (index) {
+ var i = 0;
+ while (i < this._pairs.length && this._pairs[i] !== 7)
+ i++;
+ if (i < this._pairs.length)
+ this._pairs[i] = index;
+};
+
+PairList.prototype.reset = function () {
+ for (var i = 0, l = this._pairs.length; i < l; i++)
+ this._pairs[i] = 7;
+};
+
+PairList.prototype.draw = function () {
+ SpriteGameObject.prototype.draw.call(this);
+ if (!this.visible)
+ return;
+ for (var i = 0, l = this._pairs.length; i < l; i++) {
+ this._pairSprite.position = new Vector2(110 + i * this.height, 8);
+ this._pairSprite.sheetIndex = this._pairs[i];
+ this._pairSprite.draw();
+ }
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/SpriteGameObject.js b/22_PairingPenguins/PenguinPairs5/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..ad72ce1
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/SpriteGameObject.js
@@ -0,0 +1,86 @@
+"use strict";
+
+function SpriteGameObject(sprite, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.sprite = sprite;
+
+ this.origin = Vector2.zero;
+ this.mirror = false;
+ this._sheetIndex = 0;
+}
+
+SpriteGameObject.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return this.sprite.size;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "center",
+ {
+ get: function () {
+ return this.sprite.center;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "sheetIndex",
+ {
+ get: function () {
+ return this._sheetIndex;
+ },
+ set: function (value) {
+ if (value >= 0)
+ this._sheetIndex = value % this.sprite.nrSheetElements;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(SpriteGameObject.prototype, "boundingBox",
+ {
+ get: function () {
+ var leftTop = this.worldPosition.subtractFrom((this.origin));
+ return new Rectangle(leftTop.x, leftTop.y, this.width, this.height);
+ }
+ });
+
+SpriteGameObject.prototype.draw = function () {
+ if (this._visible)
+ this.sprite.draw(this.worldPosition, this.origin, this._sheetIndex, this.mirror);
+};
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/Tile.js b/22_PairingPenguins/PenguinPairs5/gameobjects/Tile.js
new file mode 100644
index 0000000..3b19fba
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/Tile.js
@@ -0,0 +1,20 @@
+"use strict";
+
+var TileType = {
+ normal: 0,
+ background: 1,
+ wall: 2
+};
+
+function Tile(sprite, layer) {
+ SpriteGameObject.call(this, sprite, layer);
+ this.type = TileType.normal;
+}
+
+Tile.prototype = Object.create(SpriteGameObject.prototype);
+
+Tile.prototype.draw = function () {
+ if (this.type === TileType.background)
+ return;
+ SpriteGameObject.prototype.draw.call(this);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gameobjects/TileField.js b/22_PairingPenguins/PenguinPairs5/gameobjects/TileField.js
new file mode 100644
index 0000000..ddf33f8
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gameobjects/TileField.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function TileField(rows, columns, layer, id) {
+ GameObjectGrid.call(this, rows, columns, layer, id);
+}
+
+TileField.prototype = Object.create(GameObjectGrid.prototype);
+
+TileField.prototype.getTileType = function (pos) {
+ if (this.isOutsideField(pos))
+ return TileType.background;
+ return this.at(pos.x, pos.y).type;
+};
+
+TileField.prototype.isOutsideField = function (pos) {
+ return (pos.x < 0 || pos.x >= this.columns || pos.y < 0 || pos.y >= this.rows);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/geom/Rectangle.js b/22_PairingPenguins/PenguinPairs5/geom/Rectangle.js
new file mode 100644
index 0000000..b3b7f74
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/geom/Rectangle.js
@@ -0,0 +1,72 @@
+"use strict";
+
+function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+}
+
+Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new Vector2(this.x, this.y);
+ }
+ });
+
+Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new Vector2(this.width, this.height);
+ }
+ });
+
+Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+};
+
+Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+};
+
+Rectangle.prototype.draw = function () {
+ Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/geom/Vector2.js b/22_PairingPenguins/PenguinPairs5/geom/Vector2.js
new file mode 100644
index 0000000..04bf6a8
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/geom/Vector2.js
@@ -0,0 +1,114 @@
+"use strict";
+
+function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+}
+
+Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new Vector2();
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+Vector2.prototype.addTo = function (v) {
+ if (v.constructor == Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+};
+
+Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+};
+
+Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor == Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+};
+
+Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+};
+
+Vector2.prototype.divideBy = function (v) {
+ if (v.constructor == Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+};
+
+Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+};
+
+Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor == Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+};
+
+Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+};
+
+Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+};
+
+Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+};
+
+Vector2.prototype.copy = function () {
+ return new Vector2(this.x, this.y);
+};
+
+Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gui/Button.js b/22_PairingPenguins/PenguinPairs5/gui/Button.js
new file mode 100644
index 0000000..d1d9d63
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gui/Button.js
@@ -0,0 +1,18 @@
+"use strict";
+
+function Button(sprite, layer, id) {
+ SpriteGameObject.call(this, sprite, layer, id);
+
+ this.pressed = false;
+ this.down = false;
+}
+
+Button.prototype = Object.create(SpriteGameObject.prototype);
+
+Button.prototype.handleInput = function (delta) {
+ var boundingBox = this.boundingBox;
+ this.pressed = this.visible && (Touch.containsTouchPress(boundingBox) ||
+ Mouse.containsMousePress(boundingBox));
+ this.down = this.visible && (Touch.containsTouch(boundingBox) ||
+ Mouse.containsMouseDown(boundingBox));
+};
diff --git a/22_PairingPenguins/PenguinPairs5/gui/Label.js b/22_PairingPenguins/PenguinPairs5/gui/Label.js
new file mode 100644
index 0000000..41c5af6
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gui/Label.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function calculateTextSize(fontname, fontsize, text) {
+ var div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = -1000;
+ div.style.top = -1000;
+ document.body.appendChild(div);
+ text = typeof text !== 'undefined' ? text : "M";
+ div.style.fontSize = "" + fontsize;
+ div.style.fontFamily = fontname;
+ div.innerHTML = text;
+ var size = new Vector2(div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+ return size;
+}
+
+function Label(fontname, fontsize, layer, id) {
+ GameObject.call(this, layer, id);
+
+ this.color = Color.black;
+ this.origin = Vector2.zero;
+ this._fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ this._fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+ this._contents = "";
+ this._align = "left";
+ this._size = Vector2.zero;
+}
+
+Label.prototype = Object.create(GameObject.prototype);
+
+Object.defineProperty(Label.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "width",
+ {
+ get: function () {
+ return this._size.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "height",
+ {
+ get: function () {
+ return this._size.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+Object.defineProperty(Label.prototype, "screenCenter",
+ {
+ get: function () {
+ return Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+Object.defineProperty(Label.prototype, "text",
+ {
+ get: function () {
+ return this._contents;
+ },
+
+ set: function (value) {
+ this._contents = value;
+ this._size = calculateTextSize(this._fontname, this._fontsize, value);
+ }
+ });
+
+Label.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ Canvas2D.drawText(this._contents, this.worldPosition,
+ this.origin, this.color, this._align,
+ this._fontname, this._fontsize);
+};
diff --git a/22_PairingPenguins/PenguinPairs5/gui/LevelButton.js b/22_PairingPenguins/PenguinPairs5/gui/LevelButton.js
new file mode 100644
index 0000000..4ba1fcb
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gui/LevelButton.js
@@ -0,0 +1,59 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new SpriteGameObject(sprites.level_solved, ID.layer_overlays);
+ this._levelUnsolved = new SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+
+ this.add(this._levelUnsolved);
+
+ this._levelLocked = new SpriteGameObject(sprites.level_locked, ID.layer_overlays_2);
+ this.add(this._levelLocked);
+
+ var textLabel = new Label("Arial", "20px", ID.layer_overlays_1);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new Vector2(this._levelSolved.width - textLabel.width,
+ this._levelSolved.height - textLabel.height + 50).divideBy(2);
+ textLabel.color = Color.black;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (window.LEVELS[this.levelIndex].locked)
+ return;
+ if (Touch.isTouchDevice)
+ this.pressed = this.visible && Touch.containsTouchPress(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(Mouse.position);
+};
+
+LevelButton.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ var currLevel = window.LEVELS[this.levelIndex];
+ this._levelLocked.visible = currLevel.locked;
+ this._levelSolved.visible = currLevel.solved;
+ this._levelUnsolved.visible = !currLevel.solved;
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gui/OnOffButton.js b/22_PairingPenguins/PenguinPairs5/gui/OnOffButton.js
new file mode 100644
index 0000000..78d25bb
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gui/OnOffButton.js
@@ -0,0 +1,28 @@
+"use strict";
+
+function OnOffButton(sprite, layer) {
+ SpriteGameObject.call(this, sprite, layer);
+}
+
+OnOffButton.prototype = Object.create(SpriteGameObject.prototype);
+
+Object.defineProperty(OnOffButton.prototype, "on",
+ {
+ get: function () {
+ return this.sheetIndex === 1;
+ },
+ set: function (value) {
+ if (value)
+ this.sheetIndex = 1;
+ else
+ this.sheetIndex = 0;
+ }
+ });
+
+OnOffButton.prototype.handleInput = function (delta) {
+ if (!this.visible)
+ return;
+ if (Touch.containsTouchPress(this.boundingBox) ||
+ Mouse.containsMousePress(this.boundingBox))
+ this.sheetIndex = (this.sheetIndex + 1) % 2;
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/gui/Slider.js b/22_PairingPenguins/PenguinPairs5/gui/Slider.js
new file mode 100644
index 0000000..20dca1f
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/gui/Slider.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function Slider(sliderback, sliderfront, layer) {
+ GameObjectList.call(this, layer);
+ this.dragging = false;
+ this.draggingId = -1;
+ this.leftmargin = 5;
+ this.rightmargin = 7;
+
+ this.back = new SpriteGameObject(sliderback);
+ this.front = new SpriteGameObject(sliderfront, 1);
+ this.front.position = new Vector2(this.leftmargin, 8);
+ this.add(this.back);
+ this.add(this.front);
+}
+
+Slider.prototype = Object.create(GameObjectList.prototype);
+
+Object.defineProperty(Slider.prototype, "value",
+ {
+ get: function () {
+ return (this.front.position.x - this.back.position.x - this.leftmargin) /
+ (this.back.width - this.front.width - this.leftmargin - this.rightmargin);
+ },
+ set: function (value) {
+ var newxpos = value * (this.back.width - this.front.width - this.leftmargin - this.rightmargin)
+ + this.back.position.x + this.leftmargin;
+ this.front.position = new Vector2(newxpos, this.front.position.y);
+ }
+ });
+
+Slider.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (Mouse.left.down || Touch.isTouching) {
+ if (Touch.isTouchDevice) {
+ if (Touch.containsTouch(this.back.boundingBox))
+ this.draggingId = Touch.getIndexInRect((this.back.boundingBox));
+ if (Touch.containsTouch(this.back.boundingBox) || this.dragging) {
+ var touchPos = Touch.getPosition(this.draggingId);
+ this.front.position = new Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(touchPos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ this.dragging = true;
+ }
+ } else {
+ var mousePos = Mouse.position;
+ if (this.back.boundingBox.contains(mousePos) || this.dragging) {
+ this.front.position = new Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(mousePos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ this.dragging = true;
+ }
+ }
+ }
+ else {
+ this.dragging = false;
+ this.draggingId = -1;
+ }
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/input/ButtonState.js b/22_PairingPenguins/PenguinPairs5/input/ButtonState.js
new file mode 100644
index 0000000..e3094f4
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/input/ButtonState.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+}
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/input/Keyboard.js b/22_PairingPenguins/PenguinPairs5/input/Keyboard.js
new file mode 100644
index 0000000..8411e4b
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/input/Keyboard.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!Keyboard._keyStates[code].down)
+ Keyboard._keyStates[code].pressed = true;
+ Keyboard._keyStates[code].down = true;
+}
+
+function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ Keyboard._keyStates[code].down = false;
+}
+
+function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+}
+
+Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+};
+
+Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+};
+
+Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+};
+
+var Keyboard = new Keyboard_Singleton();
diff --git a/22_PairingPenguins/PenguinPairs5/input/Mouse.js b/22_PairingPenguins/PenguinPairs5/input/Mouse.js
new file mode 100644
index 0000000..662f762
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/input/Mouse.js
@@ -0,0 +1,92 @@
+"use strict";
+
+function handleMouseMove(evt) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ Mouse._position = new Vector2(mx, my);
+}
+
+function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!Mouse._left.down)
+ Mouse._left.pressed = true;
+ Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!Mouse._middle.down)
+ Mouse._middle.pressed = true;
+ Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!Mouse._right.down)
+ Mouse._right.pressed = true;
+ Mouse._right.down = true;
+ }
+}
+
+function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ Mouse._left.down = false;
+ else if (evt.which === 2)
+ Mouse._middle.down = false;
+ else if (evt.which === 3)
+ Mouse._right.down = false;
+}
+
+function Mouse_Singleton() {
+ this._position = Vector2.zero;
+ this._left = new ButtonState();
+ this._middle = new ButtonState();
+ this._right = new ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+}
+
+Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+};
+
+Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+};
+
+Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+};
+
+var Mouse = new Mouse_Singleton();
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/input/Touch.js b/22_PairingPenguins/PenguinPairs5/input/Touch.js
new file mode 100644
index 0000000..7adb4ce
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/input/Touch.js
@@ -0,0 +1,108 @@
+"use strict";
+
+function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ Touch._touches.push(touches[i]);
+ Touch._touchPresses.push(true);
+ }
+}
+
+function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1, touches[i]);
+ }
+}
+
+function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = Touch.getTouchIndexFromId(touches[i].identifier);
+ Touch._touches.splice(id, 1);
+ Touch._touchPresses.splice(id, 1);
+ }
+}
+
+function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+}
+
+Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+};
+
+Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+};
+
+Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = Canvas2D.scale;
+ var canvasOffset = Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new Vector2(mx, my);
+};
+
+Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return Vector2.zero;
+};
+
+Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+};
+
+Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+};
+
+var Touch = new Touch_Singleton();
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/states/HelpState.js b/22_PairingPenguins/PenguinPairs5/states/HelpState.js
new file mode 100644
index 0000000..60f3ca6
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/states/HelpState.js
@@ -0,0 +1,22 @@
+"use strict";
+
+function HelpState(layer) {
+ GameObjectList.call(this, layer);
+
+ // the background
+ var background = new SpriteGameObject(sprites.background_help);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new Button(sprites.button_back, 100);
+ this.backButton.position = new Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+HelpState.prototype = Object.create(GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/states/LevelMenuState.js b/22_PairingPenguins/PenguinPairs5/states/LevelMenuState.js
new file mode 100644
index 0000000..f8d2837
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/states/LevelMenuState.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ this.background = new SpriteGameObject(sprites.background_levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0; i < window.LEVELS.length; i += 1) {
+ var row = Math.floor(i / 5);
+ var column = i % 5;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new Vector2(column * (level.width + 30) + 155,
+ row * (level.height + 5) + 230);
+ //console.log("Level " + i + " at position " + level.position);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i += 1) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ var playingState = GameStateManager.get(ID.game_state_playing);
+ playingState.goToLevel(selectedLevel);
+ GameStateManager.switchTo(ID.game_state_playing);
+ }
+ else if (this.back.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/states/OptionsMenuState.js b/22_PairingPenguins/PenguinPairs5/states/OptionsMenuState.js
new file mode 100644
index 0000000..f4ea732
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/states/OptionsMenuState.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function OptionsMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ var background = new SpriteGameObject(sprites.background_options, ID.layer_background);
+ this.add(background);
+
+ var onOffLabel = new Label("Arial", "60px", ID.layer_overlays);
+ onOffLabel.text = "Hints";
+ onOffLabel.position = new Vector2(150, 360);
+ onOffLabel.color = Color.darkBlue;
+ this.add(onOffLabel);
+
+ this.onOffButton = new OnOffButton(sprites.button_offon, ID.layer_overlays);
+ this.onOffButton.position = new Vector2(650, 340);
+ this.onOffButton.on = GameSettings.hints;
+ this.add(this.onOffButton);
+
+ var musicText = new Label("Arial", "60px", ID.layer_overlays);
+ musicText.text = "Music volume";
+ musicText.position = new Vector2(150, 490);
+ musicText.color = Color.darkBlue;
+ this.add(musicText);
+
+ this.musicSlider = new Slider(sprites.slider_bar, sprites.slider_button, ID.layer_overlays);
+ this.musicSlider.position = new Vector2(650, 500);
+ this.musicSlider.value = sounds.music.volume;
+ this.add(this.musicSlider);
+
+ this.backButton = new Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+OptionsMenuState.prototype = Object.create(GameObjectList.prototype);
+
+OptionsMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ GameStateManager.switchTo(ID.game_state_title);
+};
+
+OptionsMenuState.prototype.update = function (delta) {
+ GameObjectList.prototype.update.call(this, delta);
+ sounds.music.volume = this.musicSlider.value;
+ GameSettings.hints = this.onOffButton.on;
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/states/PlayingState.js b/22_PairingPenguins/PenguinPairs5/states/PlayingState.js
new file mode 100644
index 0000000..e87ebd8
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/states/PlayingState.js
@@ -0,0 +1,59 @@
+"use strict";
+
+function PlayingState() {
+ IGameLoopObject.call(this);
+
+ this.currentLevelIndex = -1;
+ this.levels = [];
+
+ this.loadLevelsStatus();
+ this.loadLevels();
+}
+
+PlayingState.prototype = Object.create(IGameLoopObject.prototype);
+
+Object.defineProperty(PlayingState.prototype, "currentLevel", {
+ get: function () {
+ return this.levels[this.currentLevelIndex];
+ }
+});
+
+PlayingState.prototype.handleInput = function (delta) {
+ this.currentLevel.handleInput(delta);
+};
+
+PlayingState.prototype.update = function (delta) {
+ this.currentLevel.update(delta);
+};
+
+PlayingState.prototype.draw = function () {
+ this.currentLevel.draw();
+};
+
+PlayingState.prototype.reset = function () {
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.goToLevel = function (levelIndex) {
+ if (levelIndex < 0 || levelIndex >= window.LEVELS.length)
+ return;
+ this.currentLevelIndex = levelIndex;
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.loadLevels = function () {
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel++)
+ this.levels.push(new Level(currLevel));
+};
+
+PlayingState.prototype.loadLevelsStatus = function () {
+ if (localStorage && localStorage.penguinPairsLevels) {
+ window.LEVELS = JSON.parse(localStorage.penguinPairsLevels);
+ }
+};
+
+PlayingState.prototype.writeLevelsStatus = function () {
+ if (!localStorage)
+ return;
+ localStorage.penguinPairsLevels = JSON.stringify(window.LEVELS);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/states/TitleMenuState.js b/22_PairingPenguins/PenguinPairs5/states/TitleMenuState.js
new file mode 100644
index 0000000..a4eee1b
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/states/TitleMenuState.js
@@ -0,0 +1,32 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ GameObjectList.call(this, layer);
+
+ var background = new SpriteGameObject(sprites.background_title, ID.layer_background);
+ this.add(background);
+
+ this.playButton = new Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new Vector2(415, 540);
+ this.add(this.playButton);
+
+ this.optionsButton = new Button(sprites.button_options, ID.layer_overlays);
+ this.optionsButton.position = new Vector2(415, 650);
+ this.add(this.optionsButton);
+
+ this.helpButton = new Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new Vector2(415, 760);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ GameStateManager.switchTo(ID.game_state_help);
+ else if (this.optionsButton.pressed)
+ GameStateManager.switchTo(ID.game_state_options);
+};
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/system/Color.js b/22_PairingPenguins/PenguinPairs5/system/Color.js
new file mode 100644
index 0000000..19ee82a
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/system/Color.js
@@ -0,0 +1,144 @@
+"use strict";
+
+ var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+ };
\ No newline at end of file
diff --git a/22_PairingPenguins/PenguinPairs5/system/Keys.js b/22_PairingPenguins/PenguinPairs5/system/Keys.js
new file mode 100644
index 0000000..ba8c419
--- /dev/null
+++ b/22_PairingPenguins/PenguinPairs5/system/Keys.js
@@ -0,0 +1,47 @@
+"use strict";
+
+var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+};
\ No newline at end of file
diff --git a/23_FinishingGame/LAB.min.js b/23_FinishingGame/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/23_FinishingGame/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/PenguinPairs.html b/23_FinishingGame/PenguinPairsFinal/PenguinPairs.html
new file mode 100644
index 0000000..d861335
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/PenguinPairs.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+ Penguin Pairs
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/PenguinPairs.js b/23_FinishingGame/PenguinPairsFinal/PenguinPairs.js
new file mode 100644
index 0000000..32be35c
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/PenguinPairs.js
@@ -0,0 +1,95 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+var GameSettings = {
+ hints: true
+};
+
+powerupjs.Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return new powerupjs.SpriteSheet("../../assets/PenguinPairs/sprites/" + sprite);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new powerupjs.Sound("../../assets/PenguinPairs/sounds/" + sound, looping);
+ };
+
+ sprites.arrow = loadSprite("spr_arrow@4.png");
+ sprites.arrow_hover = loadSprite("spr_arrow_hover@4.png");
+ sprites.arrow_hint = loadSprite("spr_arrow_hint@4.png");
+ sprites.background_title = loadSprite("spr_background_title.jpg");
+ sprites.background_help = loadSprite("spr_background_help.jpg");
+ sprites.background_level = loadSprite("spr_background_level.jpg");
+ sprites.background_levelselect = loadSprite("spr_background_levelselect.jpg");
+ sprites.background_options = loadSprite("spr_background_options.jpg");
+ sprites.button_back = loadSprite("spr_button_back.jpg");
+ sprites.button_help = loadSprite("spr_button_help.jpg");
+ sprites.button_hint = loadSprite("spr_button_hint.png");
+ sprites.button_offon = loadSprite("spr_button_offon@2.png");
+ sprites.button_options = loadSprite("spr_button_options.jpg");
+ sprites.button_play = loadSprite("spr_button_play.jpg");
+ sprites.button_quit = loadSprite("spr_button_quit.png");
+ sprites.button_retry = loadSprite("spr_button_retry.png");
+ sprites.field = loadSprite("spr_field@2.png");
+ sprites.frame_goal = loadSprite("spr_frame_goal.jpg");
+ sprites.help = loadSprite("spr_help.jpg");
+ if (powerupjs.Touch.isTouchDevice)
+ sprites.level_finished = loadSprite("spr_level_finished_tap.png");
+ else
+ sprites.level_finished = loadSprite("spr_level_finished_click.png");
+ sprites.level_unsolved = loadSprite("spr_level_unsolved.png");
+ sprites.level_solved = loadSprite("spr_level_solved@6.png");
+ sprites.level_locked = loadSprite("spr_lock.png");
+ sprites.penguin = loadSprite("spr_penguin@8.png");
+ sprites.penguin_boxed = loadSprite("spr_penguin_boxed@8.png");
+ sprites.penguin_empty = loadSprite("spr_penguin_empty.png");
+ sprites.penguin_pairs = loadSprite("spr_penguin_pairs@8.png");
+ sprites.shark = loadSprite("spr_shark.png");
+ sprites.slider_bar = loadSprite("spr_slider_bar.jpg");
+ sprites.slider_button = loadSprite("spr_slider_button.jpg");
+ sprites.wall = loadSprite("spr_wall.png");
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.eat = loadSound("snd_eat");
+ sounds.lost = loadSound("snd_lost");
+ sounds.won = loadSound("snd_won");
+ sounds.pair = loadSound("snd_pair");
+};
+
+powerupjs.Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_objects_1 = 21;
+ ID.layer_objects_2 = 22;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.tiles = 1;
+ ID.pairList = 2;
+ ID.animalSelector = 3;
+
+ // create the different game modes
+ ID.game_state_title = powerupjs.GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = powerupjs.GameStateManager.add(new HelpState());
+ ID.game_state_options = powerupjs.GameStateManager.add(new OptionsMenuState());
+ ID.game_state_playing = powerupjs.GameStateManager.add(new PlayingState());
+ ID.game_state_levelselect = powerupjs.GameStateManager.add(new LevelMenuState());
+ ID.game_state_levelfinished = powerupjs.GameStateManager.add(new LevelFinishedState());
+
+ // set the current game mode
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/game-layout.css b/23_FinishingGame/PenguinPairsFinal/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gameobjects/Animal.js b/23_FinishingGame/PenguinPairsFinal/gameobjects/Animal.js
new file mode 100644
index 0000000..95f5e59
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gameobjects/Animal.js
@@ -0,0 +1,120 @@
+"use strict";
+
+function Animal(color, sprite, layer) {
+ powerupjs.SpriteGameObject.call(this, sprite, layer);
+
+ this.initialPosition = powerupjs.Vector2.zero;
+ this.boxed = (color === color.toUpperCase());
+ this.initialEmptyBox = this.boxed && color.toLowerCase() === "x";
+ this.sheetIndex = "brgyopmx".indexOf(color.toLowerCase());
+}
+
+Animal.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Object.defineProperty(Animal.prototype, "currentBlock",
+ {
+ get: function () {
+ var tileField = this.root.find(ID.tiles);
+ var p = new powerupjs.Vector2(Math.floor(this.position.x / tileField.cellWidth),
+ Math.floor(this.position.y / tileField.cellHeight));
+ if (this.velocity.x > 0)
+ p.x++;
+ if (this.velocity.y > 0)
+ p.y++;
+ return p;
+ }
+ });
+
+Animal.prototype.reset = function () {
+ this.position = this.initialPosition.copy();
+ this.velocity = powerupjs.Vector2.zero;
+ this.visible = true;
+ if (this.initialEmptyBox)
+ this.sheetIndex = 7;
+};
+
+Animal.prototype.handleInput = function (delta) {
+ if (!this.visible || this.boxed || this.velocity.x != 0 || this.velocity.y != 0)
+ return;
+ if (powerupjs.Touch.isTouchDevice) {
+ if (!powerupjs.Touch.containsTouchPress(this.boundingBox))
+ return;
+ } else {
+ if (!powerupjs.Mouse.left.pressed || !this.boundingBox.contains(powerupjs.Mouse.position))
+ return;
+ }
+ var animalSelector = this.root.find(ID.animalSelector);
+ if (animalSelector.visible)
+ return;
+
+ animalSelector.position = this.position;
+ animalSelector.visible = true;
+ animalSelector.selectedAnimal = this;
+ powerupjs.Mouse.reset();
+ powerupjs.Touch.reset();
+};
+
+Animal.prototype.update = function (delta) {
+ powerupjs.SpriteGameObject.prototype.update.call(this, delta);
+ if (!this.visible || this.velocity.isZero)
+ return;
+
+ var target = this.currentBlock;
+ var tileField = this.root.find(ID.tiles);
+
+ if (tileField.getTileType(target) == TileType.background) {
+ this.visible = false;
+ this.velocity = powerupjs.Vector2.zero;
+ sounds.lost.play();
+ }
+ else if (tileField.getTileType(target) == TileType.wall)
+ this.stopMoving();
+ else {
+ var a = this.root.findAnimalAtPosition(target);
+ if (a != null && a.visible) {
+ if (a.isSeal())
+ this.stopMoving();
+ else if (a.isEmptyBox()) {
+ this.visible = false;
+ a.sheetIndex = this.sheetIndex;
+ }
+ else if (a.sheetIndex == this.sheetIndex || this.isMulticolor() || a.isMulticolor()) {
+ a.visible = false;
+ this.visible = false;
+ this.root.find(ID.pairList).addPair(this.sheetIndex);
+ sounds.pair.play();
+ }
+ else
+ this.stopMoving();
+ }
+
+ var s = this.root.findSharkAtPosition(target);
+ if (s != null && s.visible) {
+ s.visible = false;
+ this.visible = false;
+ this.stopMoving();
+ sounds.eat.play();
+ }
+ }
+};
+
+Animal.prototype.stopMoving = function () {
+ var tileField = this.root.find(ID.tiles);
+ this.velocity.normalize();
+ var currBlock = this.currentBlock;
+ this.position = new powerupjs.Vector2(Math.floor(currBlock.x - this.velocity.x) * tileField.cellWidth,
+ Math.floor(currBlock.y - this.velocity.y) * tileField.cellHeight);
+ this.velocity = powerupjs.Vector2.zero;
+};
+
+Animal.prototype.isSeal = function () {
+ return this.sheetIndex === 7 && !this.boxed;
+};
+
+Animal.prototype.isMulticolor = function () {
+ return this.sheetIndex === 6 && !this.boxed;
+};
+
+Animal.prototype.isEmptyBox = function () {
+ return this.sheetIndex === 7 && this.boxed;
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gameobjects/AnimalSelector.js b/23_FinishingGame/PenguinPairsFinal/gameobjects/AnimalSelector.js
new file mode 100644
index 0000000..7230501
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gameobjects/AnimalSelector.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function AnimalSelector(layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+ this._arrowright = new Arrow(0);
+ this._arrowright.position = new powerupjs.Vector2(this._arrowright.width, 0);
+ this.add(this._arrowright);
+ this._arrowup = new Arrow(1);
+ this._arrowup.position = new powerupjs.Vector2(0, -this._arrowright.height);
+ this.add(this._arrowup);
+ this._arrowleft = new Arrow(2);
+ this._arrowleft.position = new powerupjs.Vector2(-this._arrowright.width, 0);
+ this.add(this._arrowleft);
+ this._arrowdown = new Arrow(3);
+ this._arrowdown.position = new powerupjs.Vector2(0, this._arrowright.height);
+ this.add(this._arrowdown);
+ this.selectedAnimal = null;
+ this.visible = false;
+}
+
+AnimalSelector.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+AnimalSelector.prototype.handleInput = function (delta) {
+ if (!this.visible)
+ return;
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ var animalVelocity = powerupjs.Vector2.zero;
+ if (this._arrowdown.pressed)
+ animalVelocity.y = 1;
+ else if (this._arrowup.pressed)
+ animalVelocity.y = -1;
+ else if (this._arrowleft.pressed)
+ animalVelocity.x = -1;
+ else if (this._arrowright.pressed)
+ animalVelocity.x = 1;
+ animalVelocity.multiplyWith(300);
+ if (powerupjs.Mouse.left.pressed || powerupjs.Touch.containsTouchPress(powerupjs.Game.screenRect))
+ this.visible = false;
+
+ if (this.selectedAnimal === null || animalVelocity.isZero)
+ return;
+ this.selectedAnimal.velocity = animalVelocity;
+ this.root.firstMoveMade = true;
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gameobjects/Arrow.js b/23_FinishingGame/PenguinPairsFinal/gameobjects/Arrow.js
new file mode 100644
index 0000000..d6b043c
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gameobjects/Arrow.js
@@ -0,0 +1,22 @@
+"use strict";
+
+function Arrow(sheetIndex, layer, id) {
+ powerupjs.Button.call(this, sprites.arrow, layer, id);
+ this.sheetIndex = sheetIndex;
+ this.arrowHover = new powerupjs.SpriteGameObject(sprites.arrow_hover);
+ this.arrowHover.sheetIndex = sheetIndex;
+ this.arrowHover.visible = false;
+ this.arrowHover.parent = this;
+}
+
+Arrow.prototype = Object.create(powerupjs.Button.prototype);
+
+Arrow.prototype.handleInput = function (delta) {
+ powerupjs.Button.prototype.handleInput.call(this, delta);
+ this.arrowHover.visible = !powerupjs.Touch.isTouchDevice && this.boundingBox.contains(powerupjs.Mouse.position);
+};
+
+Arrow.prototype.draw = function () {
+ powerupjs.Button.prototype.draw.call(this);
+ this.arrowHover.draw();
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gameobjects/Level.js b/23_FinishingGame/PenguinPairsFinal/gameobjects/Level.js
new file mode 100644
index 0000000..fff39b8
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gameobjects/Level.js
@@ -0,0 +1,169 @@
+"use strict";
+
+function Level(levelIndex) {
+ powerupjs.GameObjectList.call(this);
+
+ var levelData = window.LEVELS[levelIndex];
+
+ this.animals = [];
+ this.sharks = [];
+ this.levelIndex = levelIndex;
+ this.firstMoveMade = false;
+
+ this.add(new powerupjs.SpriteGameObject(sprites.background_level, ID.layer_background));
+
+ this.hintButton = new powerupjs.Button(sprites.button_hint, ID.layer_overlays);
+ this.hintButton.position = new powerupjs.Vector2(916, 20);
+ this.add(this.hintButton);
+
+ this.retryButton = new powerupjs.Button(sprites.button_retry, ID.layer_overlays);
+ this.retryButton.position = new powerupjs.Vector2(916, 20);
+ this.retryButton.visible = false;
+ this.add(this.retryButton);
+
+ this.quitButton = new powerupjs.Button(sprites.button_quit, ID.layer_overlays);
+ this.quitButton.position = new powerupjs.Vector2(1058, 20);
+ this.add(this.quitButton);
+
+ var width = levelData.tiles[0].length;
+ var height = levelData.tiles.length;
+
+ var playingField = new powerupjs.GameObjectList(ID.layer_objects);
+ playingField.position = new powerupjs.Vector2((powerupjs.Game.size.x - width * 73) / 2, 100);
+ this.add(playingField);
+
+ var hint = new powerupjs.SpriteGameObject(sprites.arrow_hint, ID.layer_objects_2);
+ hint.sheetIndex = levelData.hint_arrow_direction;
+ hint.position = new powerupjs.Vector2(levelData.hint_arrow_x * 73, levelData.hint_arrow_y * 72);
+ playingField.add(hint);
+
+ this.hintVisible = new VisibilityTimer(hint);
+ playingField.add(this.hintVisible);
+
+ var tileField = new TileField(height, width, ID.layer_objects, ID.tiles);
+ tileField.cellHeight = 72;
+ tileField.cellWidth = 73;
+ for (var row = 0; row < height; row++) {
+ for (var col = 0; col < width; col++) {
+ var t = null;
+
+ switch (levelData.tiles[row][col]) {
+ case '.' :
+ t = new Tile(sprites.field, ID.layer_objects);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ break;
+ case ' ':
+ t = new Tile(sprites.wall, ID.layer_objects);
+ t.type = TileType.background;
+ tileField.addAt(t, col, row);
+ break;
+ case 'r':
+ case 'b':
+ case 'g':
+ case 'o':
+ case 'p':
+ case 'y':
+ case 'm':
+ case 'x':
+ case 'R':
+ case 'B':
+ case 'G':
+ case 'O':
+ case 'P':
+ case 'Y':
+ case 'M':
+ case 'X':
+ t = new Tile(sprites.field, ID.layer_objects);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ var animalSprite = sprites.penguin;
+ if (levelData.tiles[row][col] === levelData.tiles[row][col].toUpperCase())
+ animalSprite = sprites.penguin_boxed;
+ var p = new Animal(levelData.tiles[row][col], animalSprite, ID.layer_objects_1);
+ p.position = t.position.copy();
+ p.initialPosition = t.position.copy();
+ playingField.add(p);
+ this.animals.push(p);
+ break;
+ case '@':
+ t = new Tile(sprites.field);
+ t.sheetIndex = row + col % 2;
+ tileField.addAt(t, col, row);
+ var s = new powerupjs.SpriteGameObject(sprites.shark, ID.layer_objects_1);
+ s.position = t.position.copy();
+ playingField.add(s);
+ this.sharks.push(s);
+ break;
+ default :
+ t = new Tile(sprites.wall, ID.layer_objects);
+ tileField.addAt(t, col, row);
+ t.type = TileType.wall;
+ break;
+ }
+ }
+ }
+ playingField.add(tileField);
+
+ playingField.add(new AnimalSelector(ID.layer_objects_2, ID.animalSelector));
+
+ var pairList = new PairList(levelData.nrPairs, ID.layer_overlays, ID.pairList);
+ pairList.position = new powerupjs.Vector2(20, 15);
+ this.add(pairList);
+
+ var helpField = new powerupjs.SpriteGameObject(sprites.help, ID.layer_overlays);
+ helpField.position = new powerupjs.Vector2(helpField.screenCenterX, 780);
+ this.add(helpField);
+ var helpLabel = new powerupjs.Label("Arial", "24px", ID.layer_overlays_1);
+ helpLabel.text = levelData.hint;
+ helpLabel.color = powerupjs.Color.darkBlue;
+ helpLabel.position = new powerupjs.Vector2(helpLabel.screenCenterX, 780 + (helpField.height - helpLabel.height) / 2);
+ this.add(helpLabel);
+}
+
+Level.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(Level.prototype, "completed",
+ {
+ get: function () {
+ return this.find(ID.pairList).completed;
+ }
+ });
+
+Level.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.hintButton.pressed)
+ this.hintVisible.startVisible();
+ else if (this.retryButton.pressed)
+ this.reset();
+ else if (this.quitButton.pressed) {
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ }
+};
+
+Level.prototype.update = function (delta) {
+ this.hintButton.visible = GameSettings.hints && !this.firstMoveMade;
+ this.retryButton.visible = !this.hintButton.visible;
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+};
+
+Level.prototype.reset = function () {
+ powerupjs.GameObjectList.prototype.reset.call(this);
+ this.firstMoveMade = false;
+};
+
+Level.prototype.findAnimalAtPosition = function (pos) {
+ for (var i = 0, j = this.animals.length; i < j; ++i)
+ if (this.animals[i].currentBlock.equals(pos) && this.animals[i].velocity.isZero)
+ return this.animals[i];
+ return null;
+};
+
+Level.prototype.findSharkAtPosition = function (pos) {
+ var tileField = this.find(ID.tiles);
+ var sharkPos = new powerupjs.Vector2(pos.x * tileField.cellWidth, pos.y * tileField.cellHeight);
+ for (var i = 0, l = this.sharks.length; i < l; ++i)
+ if (this.sharks[i].position.equals(sharkPos))
+ return this.sharks[i];
+ return null;
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gameobjects/PairList.js b/23_FinishingGame/PenguinPairsFinal/gameobjects/PairList.js
new file mode 100644
index 0000000..10bf94a
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gameobjects/PairList.js
@@ -0,0 +1,45 @@
+"use strict";
+
+function PairList(nrPairs, layer, id) {
+ powerupjs.SpriteGameObject.call(this, sprites.frame_goal, layer, id);
+ this._pairs = [];
+ for (var i = 0; i < nrPairs; i++)
+ this._pairs.push(7);
+ this._pairSprite = new powerupjs.SpriteGameObject(sprites.penguin_pairs);
+ this._pairSprite.parent = this;
+}
+PairList.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Object.defineProperty(PairList.prototype, "completed",
+ {
+ get: function () {
+ for (var i = 0, l = this._pairs.length; i < l; i++)
+ if (this._pairs[i] === 7)
+ return false;
+ return true;
+ }
+ });
+
+PairList.prototype.addPair = function (index) {
+ var i = 0;
+ while (i < this._pairs.length && this._pairs[i] !== 7)
+ i++;
+ if (i < this._pairs.length)
+ this._pairs[i] = index;
+};
+
+PairList.prototype.reset = function () {
+ for (var i = 0, l = this._pairs.length; i < l; i++)
+ this._pairs[i] = 7;
+};
+
+PairList.prototype.draw = function () {
+ powerupjs.SpriteGameObject.prototype.draw.call(this);
+ if (!this.visible)
+ return;
+ for (var i = 0, l = this._pairs.length; i < l; i++) {
+ this._pairSprite.position = new powerupjs.Vector2(110 + i * this.height, 8);
+ this._pairSprite.sheetIndex = this._pairs[i];
+ this._pairSprite.draw();
+ }
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gameobjects/Tile.js b/23_FinishingGame/PenguinPairsFinal/gameobjects/Tile.js
new file mode 100644
index 0000000..ffdc0ac
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gameobjects/Tile.js
@@ -0,0 +1,20 @@
+"use strict";
+
+var TileType = {
+ normal: 0,
+ background: 1,
+ wall: 2
+};
+
+function Tile(sprite, layer) {
+ powerupjs.SpriteGameObject.call(this, sprite, layer);
+ this.type = TileType.normal;
+}
+
+Tile.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Tile.prototype.draw = function () {
+ if (this.type === TileType.background)
+ return;
+ powerupjs.SpriteGameObject.prototype.draw.call(this);
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gameobjects/TileField.js b/23_FinishingGame/PenguinPairsFinal/gameobjects/TileField.js
new file mode 100644
index 0000000..043bedd
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gameobjects/TileField.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function TileField(rows, columns, layer, id) {
+ powerupjs.GameObjectGrid.call(this, rows, columns, layer, id);
+}
+
+TileField.prototype = Object.create(powerupjs.GameObjectGrid.prototype);
+
+TileField.prototype.getTileType = function (pos) {
+ if (this.isOutsideField(pos))
+ return TileType.background;
+ return this.at(pos.x, pos.y).type;
+};
+
+TileField.prototype.isOutsideField = function (pos) {
+ return (pos.x < 0 || pos.x >= this.columns || pos.y < 0 || pos.y >= this.rows);
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gameobjects/VisibilityTimer.js b/23_FinishingGame/PenguinPairsFinal/gameobjects/VisibilityTimer.js
new file mode 100644
index 0000000..a931536
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gameobjects/VisibilityTimer.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function VisibilityTimer(target, layer) {
+ powerupjs.GameObject.call(this, layer);
+ this.target = target;
+ this._timeLeft = 0;
+ this._totalTime = 1;
+}
+
+VisibilityTimer.prototype = Object.create(powerupjs.GameObject.prototype);
+
+VisibilityTimer.prototype.update = function (delta) {
+ this._timeLeft -= delta;
+ if (this._timeLeft <= 0)
+ this.target.visible = false;
+};
+
+VisibilityTimer.prototype.startVisible = function () {
+ this._timeLeft = this._totalTime;
+ this.target.visible = true;
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gui/LevelButton.js b/23_FinishingGame/PenguinPairsFinal/gui/LevelButton.js
new file mode 100644
index 0000000..1403bbf
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gui/LevelButton.js
@@ -0,0 +1,59 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new powerupjs.SpriteGameObject(sprites.level_solved, ID.layer_overlays);
+ this._levelUnsolved = new powerupjs.SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+
+ this.add(this._levelUnsolved);
+
+ this._levelLocked = new powerupjs.SpriteGameObject(sprites.level_locked, ID.layer_overlays_2);
+ this.add(this._levelLocked);
+
+ var textLabel = new powerupjs.Label("Arial", "20px", ID.layer_overlays_1);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new powerupjs.Vector2(this._levelSolved.width - textLabel.width,
+ this._levelSolved.height - textLabel.height + 50).divideBy(2);
+ textLabel.color = powerupjs.Color.black;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (window.LEVELS[this.levelIndex].locked)
+ return;
+ if (powerupjs.Touch.isTouchDevice)
+ this.pressed = this.visible && powerupjs.Touch.containsTouchPress(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && powerupjs.Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(powerupjs.Mouse.position);
+};
+
+LevelButton.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+ var currLevel = window.LEVELS[this.levelIndex];
+ this._levelLocked.visible = currLevel.locked;
+ this._levelSolved.visible = currLevel.solved;
+ this._levelUnsolved.visible = !currLevel.solved;
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gui/OnOffButton.js b/23_FinishingGame/PenguinPairsFinal/gui/OnOffButton.js
new file mode 100644
index 0000000..900c0e4
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gui/OnOffButton.js
@@ -0,0 +1,28 @@
+"use strict";
+
+function OnOffButton(sprite, layer) {
+ powerupjs.SpriteGameObject.call(this, sprite, layer);
+}
+
+OnOffButton.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Object.defineProperty(OnOffButton.prototype, "on",
+ {
+ get: function () {
+ return this.sheetIndex === 1;
+ },
+ set: function (value) {
+ if (value)
+ this.sheetIndex = 1;
+ else
+ this.sheetIndex = 0;
+ }
+ });
+
+OnOffButton.prototype.handleInput = function (delta) {
+ if (!this.visible)
+ return;
+ if (powerupjs.Touch.containsTouchPress(this.boundingBox) ||
+ powerupjs.Mouse.containsMousePress(this.boundingBox))
+ this.sheetIndex = (this.sheetIndex + 1) % 2;
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/gui/Slider.js b/23_FinishingGame/PenguinPairsFinal/gui/Slider.js
new file mode 100644
index 0000000..75a50b0
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/gui/Slider.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function Slider(sliderback, sliderfront, layer) {
+ powerupjs.GameObjectList.call(this, layer);
+ this.dragging = false;
+ this.draggingId = -1;
+ this.leftmargin = 5;
+ this.rightmargin = 7;
+
+ this.back = new powerupjs.SpriteGameObject(sliderback);
+ this.front = new powerupjs.SpriteGameObject(sliderfront, 1);
+ this.front.position = new powerupjs.Vector2(this.leftmargin, 8);
+ this.add(this.back);
+ this.add(this.front);
+}
+
+Slider.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(Slider.prototype, "value",
+ {
+ get: function () {
+ return (this.front.position.x - this.back.position.x - this.leftmargin) /
+ (this.back.width - this.front.width - this.leftmargin - this.rightmargin);
+ },
+ set: function (value) {
+ var newxpos = value * (this.back.width - this.front.width - this.leftmargin - this.rightmargin)
+ + this.back.position.x + this.leftmargin;
+ this.front.position = new powerupjs.Vector2(newxpos, this.front.position.y);
+ }
+ });
+
+Slider.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (powerupjs.Mouse.left.down || powerupjs.Touch.isTouching) {
+ if (powerupjs.Touch.isTouchDevice) {
+ if (powerupjs.Touch.containsTouch(this.back.boundingBox))
+ this.draggingId = powerupjs.Touch.getIndexInRect((this.back.boundingBox));
+ if (powerupjs.Touch.containsTouch(this.back.boundingBox) || this.dragging) {
+ var touchPos = powerupjs.Touch.getPosition(this.draggingId);
+ this.front.position = new powerupjs.Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(touchPos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ this.dragging = true;
+ }
+ } else {
+ var mousePos = powerupjs.Mouse.position;
+ if (this.back.boundingBox.contains(mousePos) || this.dragging) {
+ this.front.position = new powerupjs.Vector2(0, this.front.position.y);
+ this.front.position.x = Math.clamp(mousePos.x - this.back.worldPosition.x - this.front.width / 2,
+ this.back.position.x + this.leftmargin,
+ this.back.position.x + this.back.width - this.front.width - this.rightmargin);
+ this.dragging = true;
+ }
+ }
+ }
+ else {
+ this.dragging = false;
+ this.draggingId = -1;
+ }
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/states/HelpState.js b/23_FinishingGame/PenguinPairsFinal/states/HelpState.js
new file mode 100644
index 0000000..8843785
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/states/HelpState.js
@@ -0,0 +1,22 @@
+"use strict";
+
+function HelpState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the background
+ var background = new powerupjs.SpriteGameObject(sprites.background_help);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new powerupjs.Button(sprites.button_back, 100);
+ this.backButton.position = new powerupjs.Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+HelpState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/states/LevelFinishedState.js b/23_FinishingGame/PenguinPairsFinal/states/LevelFinishedState.js
new file mode 100644
index 0000000..d287ef8
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/states/LevelFinishedState.js
@@ -0,0 +1,27 @@
+"use strict";
+
+function LevelFinishedState() {
+ powerupjs.GameObjectList.call(this);
+ this.playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ this.overlay = new powerupjs.SpriteGameObject(sprites.level_finished, ID.layer_overlays);
+ this.overlay.position = this.overlay.screenCenter;
+ this.add(this.overlay);
+}
+
+LevelFinishedState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+LevelFinishedState.prototype.handleInput = function (delta) {
+ if (powerupjs.Touch.isTouchDevice) {
+ if (!powerupjs.Touch.containsTouchPress(this.overlay.boundingBox))
+ return;
+ }
+ else if (!powerupjs.Mouse.left.pressed)
+ return;
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+ this.playingState.nextLevel();
+};
+
+LevelFinishedState.prototype.draw = function () {
+ this.playingState.draw();
+ powerupjs.GameObjectList.prototype.draw.call(this);
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/states/LevelMenuState.js b/23_FinishingGame/PenguinPairsFinal/states/LevelMenuState.js
new file mode 100644
index 0000000..1b4fbe5
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/states/LevelMenuState.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ this.background = new powerupjs.SpriteGameObject(sprites.background_levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new powerupjs.Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0; i < window.LEVELS.length; i += 1) {
+ var row = Math.floor(i / 5);
+ var column = i % 5;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new powerupjs.Vector2(column * (level.width + 30) + 155,
+ row * (level.height + 5) + 230);
+ //console.log("Level " + i + " at position " + level.position);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i += 1) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ var playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ playingState.goToLevel(selectedLevel);
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+ }
+ else if (this.back.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/states/OptionsMenuState.js b/23_FinishingGame/PenguinPairsFinal/states/OptionsMenuState.js
new file mode 100644
index 0000000..d377c9f
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/states/OptionsMenuState.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function OptionsMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ var background = new powerupjs.SpriteGameObject(sprites.background_options, ID.layer_background);
+ this.add(background);
+
+ var onOffLabel = new powerupjs.Label("Arial", "60px", ID.layer_overlays);
+ onOffLabel.text = "Hints";
+ onOffLabel.position = new powerupjs.Vector2(150, 360);
+ onOffLabel.color = powerupjs.Color.darkBlue;
+ this.add(onOffLabel);
+
+ this.onOffButton = new OnOffButton(sprites.button_offon, ID.layer_overlays);
+ this.onOffButton.position = new powerupjs.Vector2(650, 340);
+ this.onOffButton.on = GameSettings.hints;
+ this.add(this.onOffButton);
+
+ var musicText = new powerupjs.Label("Arial", "60px", ID.layer_overlays);
+ musicText.text = "Music volume";
+ musicText.position = new powerupjs.Vector2(150, 490);
+ musicText.color = powerupjs.Color.darkBlue;
+ this.add(musicText);
+
+ this.musicSlider = new Slider(sprites.slider_bar, sprites.slider_button, ID.layer_overlays);
+ this.musicSlider.position = new powerupjs.Vector2(650, 500);
+ this.musicSlider.value = sounds.music.volume;
+ this.add(this.musicSlider);
+
+ this.backButton = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new powerupjs.Vector2(415, 720);
+ this.add(this.backButton);
+}
+
+OptionsMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+OptionsMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
+
+OptionsMenuState.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+ sounds.music.volume = this.musicSlider.value;
+ GameSettings.hints = this.onOffButton.on;
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/states/PlayingState.js b/23_FinishingGame/PenguinPairsFinal/states/PlayingState.js
new file mode 100644
index 0000000..c7355f1
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/states/PlayingState.js
@@ -0,0 +1,74 @@
+"use strict";
+
+function PlayingState() {
+ powerupjs.IGameLoopObject.call(this);
+
+ this.currentLevelIndex = -1;
+ this.levels = [];
+
+ this.loadLevelsStatus();
+ this.loadLevels();
+}
+
+PlayingState.prototype = Object.create(powerupjs.IGameLoopObject.prototype);
+
+Object.defineProperty(PlayingState.prototype, "currentLevel", {
+ get: function () {
+ return this.levels[this.currentLevelIndex];
+ }
+});
+
+PlayingState.prototype.handleInput = function (delta) {
+ this.currentLevel.handleInput(delta);
+};
+
+PlayingState.prototype.update = function (delta) {
+ this.currentLevel.update(delta);
+ if (this.currentLevel.completed) {
+ sounds.won.play();
+ window.LEVELS[this.currentLevelIndex].solved = true;
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelfinished);
+ }
+};
+
+PlayingState.prototype.draw = function () {
+ this.currentLevel.draw();
+};
+
+PlayingState.prototype.reset = function () {
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.goToLevel = function (levelIndex) {
+ if (levelIndex < 0 || levelIndex >= window.LEVELS.length)
+ return;
+ this.currentLevelIndex = levelIndex;
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.nextLevel = function () {
+ if (this.currentLevelIndex >= window.LEVELS.length - 1)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else {
+ this.goToLevel(this.currentLevelIndex + 1);
+ window.LEVELS[this.currentLevelIndex].locked = false;
+ }
+ this.writeLevelsStatus();
+};
+
+PlayingState.prototype.loadLevels = function () {
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel++)
+ this.levels.push(new Level(currLevel));
+};
+
+PlayingState.prototype.loadLevelsStatus = function () {
+ if (localStorage && localStorage.penguinPairsLevels) {
+ window.LEVELS = JSON.parse(localStorage.penguinPairsLevels);
+ }
+};
+
+PlayingState.prototype.writeLevelsStatus = function () {
+ if (!localStorage)
+ return;
+ localStorage.penguinPairsLevels = JSON.stringify(window.LEVELS);
+};
\ No newline at end of file
diff --git a/23_FinishingGame/PenguinPairsFinal/states/TitleMenuState.js b/23_FinishingGame/PenguinPairsFinal/states/TitleMenuState.js
new file mode 100644
index 0000000..03341f3
--- /dev/null
+++ b/23_FinishingGame/PenguinPairsFinal/states/TitleMenuState.js
@@ -0,0 +1,32 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ var background = new powerupjs.SpriteGameObject(sprites.background_title, ID.layer_background);
+ this.add(background);
+
+ this.playButton = new powerupjs.Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new powerupjs.Vector2(415, 540);
+ this.add(this.playButton);
+
+ this.optionsButton = new powerupjs.Button(sprites.button_options, ID.layer_overlays);
+ this.optionsButton.position = new powerupjs.Vector2(415, 650);
+ this.add(this.optionsButton);
+
+ this.helpButton = new powerupjs.Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new powerupjs.Vector2(415, 760);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_help);
+ else if (this.optionsButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_options);
+};
\ No newline at end of file
diff --git a/24_GameStructure/LAB.min.js b/24_GameStructure/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/24_GameStructure/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/TickTick.html b/24_GameStructure/TickTick1/TickTick.html
new file mode 100644
index 0000000..898a5fe
--- /dev/null
+++ b/24_GameStructure/TickTick1/TickTick.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+ TickTick
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/TickTick.js b/24_GameStructure/TickTick1/TickTick.js
new file mode 100644
index 0000000..84cfafe
--- /dev/null
+++ b/24_GameStructure/TickTick1/TickTick.js
@@ -0,0 +1,113 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+powerupjs.Game.loadAssets = function () {
+ var loadSprite = function (sprite, collisionMask) {
+ return new powerupjs.SpriteSheet("../../assets/TickTick/sprites/" + sprite/*, collisionMask*/);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new powerupjs.Sound("../../assets/TickTick/sounds/" + sound, looping);
+ };
+
+ sprites.background_title = loadSprite("backgrounds/spr_title.jpg");
+ sprites.background_help = loadSprite("backgrounds/spr_help.jpg");
+ sprites.background_sky = loadSprite("backgrounds/spr_sky.jpg");
+ sprites.cloud_1 = loadSprite("backgrounds/spr_cloud_1.png");
+ sprites.cloud_2 = loadSprite("backgrounds/spr_cloud_2.png");
+ sprites.cloud_3 = loadSprite("backgrounds/spr_cloud_3.png");
+ sprites.cloud_4 = loadSprite("backgrounds/spr_cloud_4.png");
+ sprites.cloud_5 = loadSprite("backgrounds/spr_cloud_5.png");
+ sprites.mountain_1 = loadSprite("backgrounds/spr_mountain_1.png");
+ sprites.mountain_2 = loadSprite("backgrounds/spr_mountain_2.png");
+ sprites.levelselect = loadSprite("backgrounds/spr_levelselect.jpg");
+
+ if (powerupjs.Touch.isTouchDevice) {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_tap.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_tap.png");
+ } else {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_click.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_click.png");
+ }
+ sprites.frame_hint = loadSprite("overlays/spr_frame_hint.png");
+ sprites.timer = loadSprite("overlays/spr_timer.png");
+
+ sprites.button_play = loadSprite("gui/spr_button_play.png");
+ sprites.button_help = loadSprite("gui/spr_button_help.png");
+ sprites.button_back = loadSprite("gui/spr_button_back.png");
+ sprites.button_quit = loadSprite("gui/spr_button_quit.png");
+ sprites.level_solved = loadSprite("gui/spr_level_solved.png");
+ sprites.level_unsolved = loadSprite("gui/spr_level_unsolved.png");
+ sprites.level_locked = loadSprite("gui/spr_level_locked.png");
+ sprites.buttons_player = loadSprite("gui/spr_buttons_player@3.png");
+
+ sprites.wall = loadSprite("tiles/spr_wall.png");
+ sprites.wall_hot = loadSprite("tiles/spr_wall_hot.png");
+ sprites.wall_ice = loadSprite("tiles/spr_wall_ice.png");
+ sprites.platform = loadSprite("tiles/spr_platform.png");
+ sprites.platform_hot = loadSprite("tiles/spr_platform_hot.png");
+ sprites.platform_ice = loadSprite("tiles/spr_platform_ice.png");
+
+ sprites.goal = loadSprite("spr_goal.png", true);
+ sprites.water = loadSprite("spr_water.png");
+
+ // player animations
+ sprites.player_idle = loadSprite("player/spr_idle.png", true);
+ sprites.player_run = loadSprite("player/spr_run@13.png", true);
+ sprites.player_jump = loadSprite("player/spr_jump@14.png", true);
+ sprites.player_celebrate = loadSprite("player/spr_celebrate@14.png");
+ sprites.player_die = loadSprite("player/spr_die@5.png");
+ sprites.player_explode = loadSprite("player/spr_explode@5x5.png");
+
+ // enemy animations
+ sprites.rocket = loadSprite("rocket/spr_rocket@3.png", true);
+ sprites.flame = loadSprite("flame/spr_flame@9.png", true);
+ sprites.turtle_sneeze = loadSprite("turtle/spr_sneeze@9.png", true);
+ sprites.turtle_idle = loadSprite("turtle/spr_idle.png", true);
+ sprites.sparky_electrocute = loadSprite("sparky/spr_electrocute@6x5.png", true);
+ sprites.sparky_idle = loadSprite("sparky/spr_idle.png", true);
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.player_die = loadSound("snd_player_die");
+ sounds.player_explode = loadSound("snd_player_explode");
+ sounds.player_fall = loadSound("snd_player_fall");
+ sounds.player_jump = loadSound("snd_player_jump");
+ sounds.player_won = loadSound("snd_player_won");
+ sounds.water_collected = loadSound("snd_water_collected");
+};
+
+powerupjs.Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.player = 1;
+ ID.timer = 2;
+ ID.tiles = 3;
+ ID.exit = 4;
+ ID.hint_timer = 5;
+ ID.button_walkleft = 6;
+ ID.button_walkright = 7;
+ ID.button_jump = 8;
+
+ ID.game_state_title = powerupjs.GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = powerupjs.GameStateManager.add(new HelpState());
+ ID.game_state_playing = powerupjs.GameStateManager.add(new PlayingState());
+ ID.game_state_levelselect = powerupjs.GameStateManager.add(new LevelMenuState());
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/game-layout.css b/24_GameStructure/TickTick1/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/24_GameStructure/TickTick1/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/gameobjects/Tile.js b/24_GameStructure/TickTick1/gameobjects/Tile.js
new file mode 100644
index 0000000..19a1c2e
--- /dev/null
+++ b/24_GameStructure/TickTick1/gameobjects/Tile.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var TileType = {
+ background: 0,
+ normal: 1,
+ platform: 2
+};
+
+function Tile(sprite, tileTp, layer, id) {
+ sprite = typeof sprite !== 'undefined' ? sprite : sprites.wall;
+ powerupjs.SpriteGameObject.call(this, sprite, layer, id);
+
+ this.hot = false;
+ this.ice = false;
+ this.type = typeof tileTp !== 'undefined' ? tileTp : TileType.background;
+}
+
+Tile.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Tile.prototype.draw = function () {
+ if (this.type == TileType.background)
+ return;
+ powerupjs.SpriteGameObject.prototype.draw.call(this);
+};
diff --git a/24_GameStructure/TickTick1/gameobjects/WaterDrop.js b/24_GameStructure/TickTick1/gameobjects/WaterDrop.js
new file mode 100644
index 0000000..a4e2b6a
--- /dev/null
+++ b/24_GameStructure/TickTick1/gameobjects/WaterDrop.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function WaterDrop(layer, id) {
+ powerupjs.SpriteGameObject.call(this, sprites.water, layer, id);
+ this._bounce = 0;
+}
+
+WaterDrop.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+WaterDrop.prototype.update = function (delta) {
+ this.position.y -= this._bounce;
+ var t = powerupjs.Game.totalTime * 3 + this.position.x;
+ this._bounce = Math.sin(t) * 5;
+ this.position.y += this._bounce;
+ powerupjs.SpriteGameObject.prototype.update.call(this, delta);
+};
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/gui/LevelButton.js b/24_GameStructure/TickTick1/gui/LevelButton.js
new file mode 100644
index 0000000..45e419a
--- /dev/null
+++ b/24_GameStructure/TickTick1/gui/LevelButton.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new powerupjs.SpriteGameObject(sprites.level_solved, ID.layer_overlays_1);
+ this._levelUnsolved = new powerupjs.SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+ this.add(this._levelUnsolved);
+
+ this._levelLocked = new powerupjs.SpriteGameObject(sprites.level_locked, ID.layer_overlays_1);
+ this.add(this._levelLocked);
+
+ var textLabel = new powerupjs.Label("Arial", "20px", ID.layer_overlays_2);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new powerupjs.Vector2(this._levelSolved.width - textLabel.width - 10, 10);
+ textLabel.color = powerupjs.Color.white;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (window.LEVELS[this.levelIndex].locked)
+ return;
+ if (powerupjs.Touch.isTouchDevice)
+ this.pressed = this.visible && powerupjs.Touch.containsTouch(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && powerupjs.Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(powerupjs.Mouse.position);
+};
+
+LevelButton.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+ var currLevel = window.LEVELS[this.levelIndex];
+ this._levelLocked.visible = currLevel.locked;
+ this._levelSolved.visible = currLevel.solved;
+ this._levelUnsolved.visible = !currLevel.solved;
+};
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/level/Level.js b/24_GameStructure/TickTick1/level/Level.js
new file mode 100644
index 0000000..f808776
--- /dev/null
+++ b/24_GameStructure/TickTick1/level/Level.js
@@ -0,0 +1,109 @@
+"use strict";
+
+function Level(levelIndex, id) {
+ powerupjs.GameObjectList.call(this, id);
+
+ this._levelIndex = levelIndex;
+ this._waterdrops = new powerupjs.GameObjectList(ID.layer_objects);
+
+ this._quitButton = new powerupjs.Button(sprites.button_quit, ID.layer_overlays);
+ this._quitButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - this._quitButton.width - 10, 10);
+ this.add(this._quitButton);
+
+ this.add(this._waterdrops);
+
+ // backgrounds
+ var backgrounds = new powerupjs.GameObjectList(ID.layer_background);
+ var background_main = new powerupjs.SpriteGameObject(sprites.background_sky, ID.layer_background_1);
+ background_main.position = new powerupjs.Vector2(0, powerupjs.Game.size.y - background_main.height);
+ backgrounds.add(background_main);
+ this.add(backgrounds);
+
+ this.loadTiles();
+}
+
+Level.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Level.prototype.loadTiles = function () {
+ var levelData = window.LEVELS[this._levelIndex];
+
+ var hintField = new powerupjs.GameObjectList(ID.layer_overlays);
+ this.add(hintField);
+ var hintFrame = new powerupjs.SpriteGameObject(sprites.frame_hint, ID.layer_overlays_1);
+ hintField.position = new powerupjs.Vector2(hintFrame.screenCenterX, 10);
+ hintField.add(hintFrame);
+ var hintText = new powerupjs.Label("Arial", "14pt", ID.layer_overlays_2);
+ hintText.text = levelData.hint;
+ hintText.position = new powerupjs.Vector2(120, 25);
+ hintText.color = powerupjs.Color.black;
+ hintField.add(hintText);
+
+ var tiles = new powerupjs.GameObjectGrid(levelData.tiles.length, levelData.tiles[0].length, 1, ID.tiles);
+ this.add(tiles);
+ tiles.cellWidth = 72;
+ tiles.cellHeight = 55;
+ for (var y = 0, ly = tiles.rows; y < ly; ++y)
+ for (var x = 0, lx = tiles.columns; x < lx; ++x) {
+ var t = this.loadTile(levelData.tiles[y][x], x, y);
+ tiles.add(t, x, y);
+ }
+};
+
+Level.prototype.loadTile = function (tileType, x, y) {
+ switch (tileType) {
+ case '.':
+ return new Tile();
+ case '-':
+ return this.loadBasicTile(sprites.platform, TileType.platform);
+ case '+':
+ return this.loadBasicTile(sprites.platform_hot, TileType.platform, true, false);
+ case '@':
+ return this.loadBasicTile(sprites.platform_ice, TileType.platform, false, true);
+ case 'X':
+ return this.loadEndTile(x, y);
+ case 'W':
+ return this.loadWaterTile(x, y);
+ case '#':
+ return this.loadBasicTile(sprites.wall, TileType.normal);
+ case '^':
+ return this.loadBasicTile(sprites.wall_hot, TileType.normal, true, false);
+ case '*':
+ return this.loadBasicTile(sprites.wall_ice, TileType.normal, false, true);
+ default:
+ return new Tile();
+ }
+};
+
+Level.prototype.loadBasicTile = function (id, tileType, hot, ice) {
+ var t = new Tile(id, tileType);
+ t.hot = hot;
+ t.ice = ice;
+ return t;
+};
+
+Level.prototype.loadEndTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var exit = new powerupjs.SpriteGameObject(sprites.goal, ID.layer_objects, ID.exit);
+ exit.position = new powerupjs.Vector2(x * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ exit.origin = new powerupjs.Vector2(0, exit.height);
+ this.add(exit);
+ return new Tile();
+};
+
+Level.prototype.loadWaterTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var w = new WaterDrop(ID.layer_objects);
+ w.origin = w.center.copy();
+ w.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 0.5) * tiles.cellHeight - 10);
+ this._waterdrops.add(w);
+ return new Tile();
+};
+
+Level.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ if (!this._quitButton.pressed)
+ return;
+ this.reset();
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+};
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/states/HelpState.js b/24_GameStructure/TickTick1/states/HelpState.js
new file mode 100644
index 0000000..c71e040
--- /dev/null
+++ b/24_GameStructure/TickTick1/states/HelpState.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function HelpState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the background
+ var background = new powerupjs.SpriteGameObject(sprites.background_help, ID.layer_background);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new powerupjs.Vector2(this.backButton.screenCenterX, 750);
+ this.add(this.backButton);
+}
+HelpState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/states/LevelMenuState.js b/24_GameStructure/TickTick1/states/LevelMenuState.js
new file mode 100644
index 0000000..c00b239
--- /dev/null
+++ b/24_GameStructure/TickTick1/states/LevelMenuState.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ this.background = new powerupjs.SpriteGameObject(sprites.levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new powerupjs.Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0, j = window.LEVELS.length; i < j; ++i) {
+ var row = Math.floor(i / 4);
+ var column = i % 4;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new powerupjs.Vector2(column * (level.width + 20) + 390, row * (level.height + 20) + 180);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i++) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ var playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ playingState.goToLevel(selectedLevel);
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+ }
+ else if (this.back.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/states/PlayingState.js b/24_GameStructure/TickTick1/states/PlayingState.js
new file mode 100644
index 0000000..bc7aae4
--- /dev/null
+++ b/24_GameStructure/TickTick1/states/PlayingState.js
@@ -0,0 +1,70 @@
+"use strict";
+
+function PlayingState() {
+ powerupjs.IGameLoopObject.call(this);
+
+ this.currentLevelIndex = -1;
+ this.levels = [];
+
+ this.loadLevelsStatus();
+ this.loadLevels();
+}
+
+PlayingState.prototype = Object.create(powerupjs.IGameLoopObject.prototype);
+
+Object.defineProperty(PlayingState.prototype, "currentLevel", {
+ get: function () {
+ return this.levels[this.currentLevelIndex];
+ }
+});
+
+PlayingState.prototype.handleInput = function (delta) {
+ this.currentLevel.handleInput(delta);
+};
+
+PlayingState.prototype.update = function (delta) {
+ this.currentLevel.update(delta);
+};
+
+PlayingState.prototype.draw = function () {
+ this.currentLevel.draw();
+};
+
+PlayingState.prototype.reset = function () {
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.goToLevel = function (levelIndex) {
+ if (levelIndex < 0 || levelIndex >= this.levels.length)
+ return;
+ this.currentLevelIndex = levelIndex;
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.nextLevel = function () {
+ if (this.currentLevelIndex >= window.LEVELS.length - 1)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else {
+ this.goToLevel(this.currentLevelIndex + 1);
+ window.LEVELS[this.currentLevelIndex].locked = false;
+ }
+ this.writeLevelsStatus();
+};
+
+PlayingState.prototype.loadLevels = function () {
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel += 1) {
+ this.levels.push(new Level(currLevel));
+ }
+};
+
+PlayingState.prototype.loadLevelsStatus = function () {
+ if (localStorage && localStorage.tickTickLevels) {
+ window.LEVELS = JSON.parse(localStorage.tickTickLevels);
+ }
+};
+
+PlayingState.prototype.writeLevelsStatus = function () {
+ if (!localStorage)
+ return;
+ localStorage.tickTickLevels = JSON.stringify(window.LEVELS);
+};
\ No newline at end of file
diff --git a/24_GameStructure/TickTick1/states/TitleMenuState.js b/24_GameStructure/TickTick1/states/TitleMenuState.js
new file mode 100644
index 0000000..0628cb6
--- /dev/null
+++ b/24_GameStructure/TickTick1/states/TitleMenuState.js
@@ -0,0 +1,29 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the title screen
+ var titleScreen = new powerupjs.SpriteGameObject(sprites.background_title, ID.layer_background);
+ this.add(titleScreen);
+
+ // add a play button
+ this.playButton = new powerupjs.Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new powerupjs.Vector2(this.playButton.screenCenterX, 540);
+ this.add(this.playButton);
+
+ // add a help button
+ this.helpButton = new powerupjs.Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new powerupjs.Vector2(this.helpButton.screenCenterX, 600);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_help);
+};
\ No newline at end of file
diff --git a/25_Animation/AnimationSample/AnimationSample.html b/25_Animation/AnimationSample/AnimationSample.html
new file mode 100644
index 0000000..f89674f
--- /dev/null
+++ b/25_Animation/AnimationSample/AnimationSample.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+ Animation Sample
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/25_Animation/AnimationSample/AnimationSample.js b/25_Animation/AnimationSample/AnimationSample.js
new file mode 100644
index 0000000..f957da5
--- /dev/null
+++ b/25_Animation/AnimationSample/AnimationSample.js
@@ -0,0 +1,18 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+
+powerupjs.Game.loadAssets = function () {
+ var loadSprite = function (sprite) {
+ return new powerupjs.SpriteSheet("../../assets/TickTick/sprites/player/" + sprite);
+ };
+
+ sprites.run = loadSprite("spr_run@13.png");
+ sprites.idle = loadSprite("spr_idle.png");
+};
+
+powerupjs.Game.initialize = function () {
+ ID.game_state_animation = powerupjs.GameStateManager.add(new AnimationState());
+ powerupjs.GameStateManager.switchTo(ID.game_state_animation);
+};
\ No newline at end of file
diff --git a/25_Animation/AnimationSample/AnimationState.js b/25_Animation/AnimationSample/AnimationState.js
new file mode 100644
index 0000000..263ba5d
--- /dev/null
+++ b/25_Animation/AnimationSample/AnimationState.js
@@ -0,0 +1,11 @@
+"use strict";
+
+function AnimationState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ var player = new Player();
+ player.position = new powerupjs.Vector2(50, 300);
+ this.add(player);
+}
+
+AnimationState.prototype = Object.create(powerupjs.GameObjectList.prototype);
diff --git a/25_Animation/AnimationSample/Player.js b/25_Animation/AnimationSample/Player.js
new file mode 100644
index 0000000..09daf58
--- /dev/null
+++ b/25_Animation/AnimationSample/Player.js
@@ -0,0 +1,33 @@
+"use strict";
+
+function Player() {
+ powerupjs.AnimatedGameObject.call(this);
+
+ this.loadAnimation(sprites.idle, "idle", true);
+ this.loadAnimation(sprites.run, "run", true, 0.05);
+ this.playAnimation("idle");
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+}
+
+Player.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Player.prototype.handleInput = function (delta) {
+ var walkingSpeed = 400;
+ if (powerupjs.Keyboard.down(powerupjs.Keys.left))
+ this.velocity.x = -walkingSpeed;
+ else if (powerupjs.Keyboard.down(powerupjs.Keys.right))
+ this.velocity.x = walkingSpeed;
+ else
+ this.velocity.x = 0;
+ if (this.velocity.x != 0)
+ this.mirror = this.velocity.x < 0;
+};
+
+Player.prototype.update = function (delta) {
+ if (this.velocity.x === 0)
+ this.playAnimation("idle");
+ else
+ this.playAnimation("run");
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+};
\ No newline at end of file
diff --git a/25_Animation/AnimationSample/game-layout.css b/25_Animation/AnimationSample/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/25_Animation/AnimationSample/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/25_Animation/LAB.min.js b/25_Animation/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/25_Animation/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/26_GamePhysics/CollisionTest/CollisionTest.html b/26_GamePhysics/CollisionTest/CollisionTest.html
new file mode 100644
index 0000000..fd9c474
--- /dev/null
+++ b/26_GamePhysics/CollisionTest/CollisionTest.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+ Collision Test
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/26_GamePhysics/CollisionTest/CollisionTest.js b/26_GamePhysics/CollisionTest/CollisionTest.js
new file mode 100644
index 0000000..b9010c3
--- /dev/null
+++ b/26_GamePhysics/CollisionTest/CollisionTest.js
@@ -0,0 +1,28 @@
+/*
+
+ Note that if you run the CollisionTest example from a local file, it only works in browsers that allow
+ reading canvas pixel data we a web application is run locally. Chrome doesn't allow this for example, but
+ Firefox and Internet Explorer do.
+
+ */
+
+"use strict";
+
+var ID = {};
+var sprites = {};
+
+powerupjs.Game.loadAssets = function () {
+ var loadSprite = function (sprite, collisionMask) {
+ return new powerupjs.SpriteSheet("assets/sprites/" + sprite, collisionMask);
+ };
+
+ sprites.run = loadSprite("spr_run@13.png", true);
+ sprites.idle = loadSprite("spr_idle.png", true);
+ sprites.sparky_idle = loadSprite("spr_sparky_idle.png", true);
+ sprites.sparky_electrocute = loadSprite("spr_sparky_electrocute@6x5.png");
+};
+
+powerupjs.Game.initialize = function () {
+ ID.game_state_main = powerupjs.GameStateManager.add(new MainState());
+ powerupjs.GameStateManager.switchTo(ID.game_state_main);
+};
\ No newline at end of file
diff --git a/26_GamePhysics/CollisionTest/MainState.js b/26_GamePhysics/CollisionTest/MainState.js
new file mode 100644
index 0000000..28172ad
--- /dev/null
+++ b/26_GamePhysics/CollisionTest/MainState.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function MainState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ this.player = new Player();
+ this.player.position = new powerupjs.Vector2(50, 300);
+ this.add(this.player);
+
+ this.sparky = new Sparky();
+ this.sparky.position = new powerupjs.Vector2(150, 200);
+ this.add(this.sparky);
+}
+
+MainState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+MainState.prototype.draw = function () {
+ powerupjs.GameObjectList.prototype.draw.call(this);
+ // draw the bounding boxes
+ this.player.boundingBox.draw();
+ this.sparky.boundingBox.draw();
+ if (this.player.collidesWith(this.sparky))
+ powerupjs.Canvas2D.drawText("Collision");
+};
\ No newline at end of file
diff --git a/26_GamePhysics/CollisionTest/Player.js b/26_GamePhysics/CollisionTest/Player.js
new file mode 100644
index 0000000..312a868
--- /dev/null
+++ b/26_GamePhysics/CollisionTest/Player.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function Player() {
+ powerupjs.AnimatedGameObject.call(this);
+
+ this.loadAnimation(sprites.idle, "idle", true);
+ this.loadAnimation(sprites.run, "run", true, 0.05);
+ this.playAnimation("idle");
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+}
+Player.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Player.prototype.handleInput = function (delta) {
+ this.position = powerupjs.Mouse.position;
+};
\ No newline at end of file
diff --git a/26_GamePhysics/CollisionTest/README.txt b/26_GamePhysics/CollisionTest/README.txt
new file mode 100644
index 0000000..03c3f32
--- /dev/null
+++ b/26_GamePhysics/CollisionTest/README.txt
@@ -0,0 +1 @@
+Note that if you run the CollisionTest example from a local file, it only works in browsers that allow reading canvas pixel data we a web application is run locally. Chrome doesn't allow this for example, but Firefox and Internet Explorer do.
\ No newline at end of file
diff --git a/26_GamePhysics/CollisionTest/Sparky.js b/26_GamePhysics/CollisionTest/Sparky.js
new file mode 100644
index 0000000..4e069c9
--- /dev/null
+++ b/26_GamePhysics/CollisionTest/Sparky.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function Sparky() {
+ powerupjs.AnimatedGameObject.call(this);
+
+ this.curr = "idle";
+ this.loadAnimation(sprites.sparky_idle, "idle", true);
+ this.loadAnimation(sprites.sparky_electrocute, "electrocute", true);
+
+ this.playAnimation(this.curr);
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+}
+
+Sparky.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Sparky.prototype.handleInput = function (delta) {
+ if (powerupjs.Keyboard.pressed(powerupjs.Keys.space)) {
+ if (this.curr === "electrocute")
+ this.curr = "idle";
+ else
+ this.curr = "electrocute";
+ this.playAnimation(this.curr)
+ }
+};
\ No newline at end of file
diff --git a/26_GamePhysics/CollisionTest/assets/sprites/spr_idle.png b/26_GamePhysics/CollisionTest/assets/sprites/spr_idle.png
new file mode 100644
index 0000000..0d10bfe
Binary files /dev/null and b/26_GamePhysics/CollisionTest/assets/sprites/spr_idle.png differ
diff --git a/26_GamePhysics/CollisionTest/assets/sprites/spr_run@13.png b/26_GamePhysics/CollisionTest/assets/sprites/spr_run@13.png
new file mode 100644
index 0000000..b2f6e34
Binary files /dev/null and b/26_GamePhysics/CollisionTest/assets/sprites/spr_run@13.png differ
diff --git a/26_GamePhysics/CollisionTest/assets/sprites/spr_sparky_electrocute@6x5.png b/26_GamePhysics/CollisionTest/assets/sprites/spr_sparky_electrocute@6x5.png
new file mode 100644
index 0000000..5a63819
Binary files /dev/null and b/26_GamePhysics/CollisionTest/assets/sprites/spr_sparky_electrocute@6x5.png differ
diff --git a/26_GamePhysics/CollisionTest/assets/sprites/spr_sparky_idle.png b/26_GamePhysics/CollisionTest/assets/sprites/spr_sparky_idle.png
new file mode 100644
index 0000000..03a457c
Binary files /dev/null and b/26_GamePhysics/CollisionTest/assets/sprites/spr_sparky_idle.png differ
diff --git a/26_GamePhysics/CollisionTest/game-layout.css b/26_GamePhysics/CollisionTest/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/26_GamePhysics/CollisionTest/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/26_GamePhysics/LAB.min.js b/26_GamePhysics/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/26_GamePhysics/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/TickTick.html b/26_GamePhysics/TickTick2/TickTick.html
new file mode 100644
index 0000000..6d4996d
--- /dev/null
+++ b/26_GamePhysics/TickTick2/TickTick.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+ TickTick
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/TickTick.js b/26_GamePhysics/TickTick2/TickTick.js
new file mode 100644
index 0000000..84cfafe
--- /dev/null
+++ b/26_GamePhysics/TickTick2/TickTick.js
@@ -0,0 +1,113 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+powerupjs.Game.loadAssets = function () {
+ var loadSprite = function (sprite, collisionMask) {
+ return new powerupjs.SpriteSheet("../../assets/TickTick/sprites/" + sprite/*, collisionMask*/);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new powerupjs.Sound("../../assets/TickTick/sounds/" + sound, looping);
+ };
+
+ sprites.background_title = loadSprite("backgrounds/spr_title.jpg");
+ sprites.background_help = loadSprite("backgrounds/spr_help.jpg");
+ sprites.background_sky = loadSprite("backgrounds/spr_sky.jpg");
+ sprites.cloud_1 = loadSprite("backgrounds/spr_cloud_1.png");
+ sprites.cloud_2 = loadSprite("backgrounds/spr_cloud_2.png");
+ sprites.cloud_3 = loadSprite("backgrounds/spr_cloud_3.png");
+ sprites.cloud_4 = loadSprite("backgrounds/spr_cloud_4.png");
+ sprites.cloud_5 = loadSprite("backgrounds/spr_cloud_5.png");
+ sprites.mountain_1 = loadSprite("backgrounds/spr_mountain_1.png");
+ sprites.mountain_2 = loadSprite("backgrounds/spr_mountain_2.png");
+ sprites.levelselect = loadSprite("backgrounds/spr_levelselect.jpg");
+
+ if (powerupjs.Touch.isTouchDevice) {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_tap.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_tap.png");
+ } else {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_click.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_click.png");
+ }
+ sprites.frame_hint = loadSprite("overlays/spr_frame_hint.png");
+ sprites.timer = loadSprite("overlays/spr_timer.png");
+
+ sprites.button_play = loadSprite("gui/spr_button_play.png");
+ sprites.button_help = loadSprite("gui/spr_button_help.png");
+ sprites.button_back = loadSprite("gui/spr_button_back.png");
+ sprites.button_quit = loadSprite("gui/spr_button_quit.png");
+ sprites.level_solved = loadSprite("gui/spr_level_solved.png");
+ sprites.level_unsolved = loadSprite("gui/spr_level_unsolved.png");
+ sprites.level_locked = loadSprite("gui/spr_level_locked.png");
+ sprites.buttons_player = loadSprite("gui/spr_buttons_player@3.png");
+
+ sprites.wall = loadSprite("tiles/spr_wall.png");
+ sprites.wall_hot = loadSprite("tiles/spr_wall_hot.png");
+ sprites.wall_ice = loadSprite("tiles/spr_wall_ice.png");
+ sprites.platform = loadSprite("tiles/spr_platform.png");
+ sprites.platform_hot = loadSprite("tiles/spr_platform_hot.png");
+ sprites.platform_ice = loadSprite("tiles/spr_platform_ice.png");
+
+ sprites.goal = loadSprite("spr_goal.png", true);
+ sprites.water = loadSprite("spr_water.png");
+
+ // player animations
+ sprites.player_idle = loadSprite("player/spr_idle.png", true);
+ sprites.player_run = loadSprite("player/spr_run@13.png", true);
+ sprites.player_jump = loadSprite("player/spr_jump@14.png", true);
+ sprites.player_celebrate = loadSprite("player/spr_celebrate@14.png");
+ sprites.player_die = loadSprite("player/spr_die@5.png");
+ sprites.player_explode = loadSprite("player/spr_explode@5x5.png");
+
+ // enemy animations
+ sprites.rocket = loadSprite("rocket/spr_rocket@3.png", true);
+ sprites.flame = loadSprite("flame/spr_flame@9.png", true);
+ sprites.turtle_sneeze = loadSprite("turtle/spr_sneeze@9.png", true);
+ sprites.turtle_idle = loadSprite("turtle/spr_idle.png", true);
+ sprites.sparky_electrocute = loadSprite("sparky/spr_electrocute@6x5.png", true);
+ sprites.sparky_idle = loadSprite("sparky/spr_idle.png", true);
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.player_die = loadSound("snd_player_die");
+ sounds.player_explode = loadSound("snd_player_explode");
+ sounds.player_fall = loadSound("snd_player_fall");
+ sounds.player_jump = loadSound("snd_player_jump");
+ sounds.player_won = loadSound("snd_player_won");
+ sounds.water_collected = loadSound("snd_water_collected");
+};
+
+powerupjs.Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.player = 1;
+ ID.timer = 2;
+ ID.tiles = 3;
+ ID.exit = 4;
+ ID.hint_timer = 5;
+ ID.button_walkleft = 6;
+ ID.button_walkright = 7;
+ ID.button_jump = 8;
+
+ ID.game_state_title = powerupjs.GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = powerupjs.GameStateManager.add(new HelpState());
+ ID.game_state_playing = powerupjs.GameStateManager.add(new PlayingState());
+ ID.game_state_levelselect = powerupjs.GameStateManager.add(new LevelMenuState());
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/game-layout.css b/26_GamePhysics/TickTick2/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/26_GamePhysics/TickTick2/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/gameobjects/Player.js b/26_GamePhysics/TickTick2/gameobjects/Player.js
new file mode 100644
index 0000000..e5e8189
--- /dev/null
+++ b/26_GamePhysics/TickTick2/gameobjects/Player.js
@@ -0,0 +1,133 @@
+"use strict";
+
+function Player(start, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+ this._startPosition = start;
+
+ this.loadAnimation(sprites.player_idle, "idle", true);
+ this.loadAnimation(sprites.player_run, "run", true, 0.05);
+ this.loadAnimation(sprites.player_jump, "jump", false, 0.05);
+ this.loadAnimation(sprites.player_celebrate, "celebrate", false, 0.05);
+ this.loadAnimation(sprites.player_die, "die", false);
+ this.loadAnimation(sprites.player_explode, "explode", false, 0.04);
+
+ this.reset();
+}
+
+Player.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Player.prototype.reset = function () {
+ this.playAnimation("idle");
+
+ this._previousYPosition = this.boundingBox.bottom;
+ this.position = this._startPosition.copy();
+ this.velocity = powerupjs.Vector2.zero;
+
+ this.onTheGround = true;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+};
+
+Player.prototype.handleInput = function (delta) {
+ var walkingSpeed = 400;
+ if (this.walkingOnIce) {
+ walkingSpeed *= 1.5;
+ this.velocity.x = Math.sign(this.velocity.x) * walkingSpeed;
+ } else if (this.onTheGround)
+ this.velocity.x = 0;
+
+ if (powerupjs.Touch.isTouchDevice) {
+ // get the buttons
+ var walkLeftButton = this.root.find(ID.button_walkleft);
+ var walkRightButton = this.root.find(ID.button_walkright);
+ if (walkLeftButton.down)
+ this.velocity.x = -walkingSpeed;
+ else if (walkRightButton.down)
+ this.velocity.x = walkingSpeed;
+ } else {
+ if (powerupjs.Keyboard.down(powerupjs.Keys.left))
+ this.velocity.x = -walkingSpeed;
+ else if (powerupjs.Keyboard.down(powerupjs.Keys.right))
+ this.velocity.x = walkingSpeed;
+ }
+
+ if (this.velocity.x != 0)
+ this.mirror = this.velocity.x < 0;
+
+ if (powerupjs.Touch.isTouchDevice) {
+ var jumpButton = this.root.find(ID.button_jump);
+ if (jumpButton.pressed && this.onTheGround)
+ this.jump();
+ } else {
+ if (powerupjs.Keyboard.pressed(powerupjs.Keys.space) && this.onTheGround)
+ this.jump();
+ }
+};
+
+Player.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ this.doPhysics();
+ if (this.onTheGround)
+ if (this.velocity.x == 0)
+ this.playAnimation("idle");
+ else
+ this.playAnimation("run");
+ else if (this.velocity.y < 0)
+ this.playAnimation("jump");
+};
+
+Player.prototype.jump = function (speed) {
+ sounds.player_jump.play();
+ speed = typeof speed !== 'undefined' ? speed : 1100;
+ this.velocity.y = -speed;
+};
+
+Player.prototype.doPhysics = function () {
+ this.velocity.y += 55;
+ this.handleCollisions();
+};
+
+Player.prototype.handleCollisions = function () {
+ this.onTheGround = false;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+
+ var tiles = this.root.find(ID.tiles);
+
+ var x_floor = Math.floor(this.position.x / tiles.cellWidth);
+ var y_floor = Math.floor(this.position.y / tiles.cellHeight);
+
+ for (var y = y_floor - 2; y <= y_floor + 1; ++y)
+ for (var x = x_floor - 1; x <= x_floor + 1; ++x) {
+ var tileType = tiles.getTileType(x, y);
+ if (tileType === TileType.background)
+ continue;
+ var tileBounds = new powerupjs.Rectangle(x * tiles.cellWidth, y * tiles.cellHeight,
+ tiles.cellWidth, tiles.cellHeight);
+ var boundingBox = this.boundingBox;
+ boundingBox.height += 1;
+ if (!tileBounds.intersects(boundingBox))
+ continue;
+ var depth = boundingBox.calculateIntersectionDepth(tileBounds);
+ if (Math.abs(depth.x) < Math.abs(depth.y)) {
+ if (tileType === TileType.normal)
+ this.position.x += depth.x;
+ continue;
+ }
+ if (this._previousYPosition <= tileBounds.top && tileType !== TileType.background) {
+ this.onTheGround = true;
+ this.velocity.y = 0;
+ var currentTile = tiles.at(x, y);
+ if (currentTile !== null) {
+ this.walkingOnIce = this.walkingOnIce || currentTile.ice;
+ this.walkingOnHot = this.walkingOnHot || currentTile.hot;
+ }
+ }
+ if (tileType === TileType.normal || this.onTheGround)
+ this.position.y += depth.y + 1;
+ }
+ this._previousYPosition = this.position.y;
+};
+
diff --git a/26_GamePhysics/TickTick2/gameobjects/Tile.js b/26_GamePhysics/TickTick2/gameobjects/Tile.js
new file mode 100644
index 0000000..19a1c2e
--- /dev/null
+++ b/26_GamePhysics/TickTick2/gameobjects/Tile.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var TileType = {
+ background: 0,
+ normal: 1,
+ platform: 2
+};
+
+function Tile(sprite, tileTp, layer, id) {
+ sprite = typeof sprite !== 'undefined' ? sprite : sprites.wall;
+ powerupjs.SpriteGameObject.call(this, sprite, layer, id);
+
+ this.hot = false;
+ this.ice = false;
+ this.type = typeof tileTp !== 'undefined' ? tileTp : TileType.background;
+}
+
+Tile.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Tile.prototype.draw = function () {
+ if (this.type == TileType.background)
+ return;
+ powerupjs.SpriteGameObject.prototype.draw.call(this);
+};
diff --git a/26_GamePhysics/TickTick2/gameobjects/TileField.js b/26_GamePhysics/TickTick2/gameobjects/TileField.js
new file mode 100644
index 0000000..83b0fe1
--- /dev/null
+++ b/26_GamePhysics/TickTick2/gameobjects/TileField.js
@@ -0,0 +1,15 @@
+"use strict";
+
+function TileField(rows, columns, layer, id) {
+ powerupjs.GameObjectGrid.call(this, rows, columns, layer, id);
+}
+
+TileField.prototype = Object.create(powerupjs.GameObjectGrid.prototype);
+
+TileField.prototype.getTileType = function (x, y) {
+ if (x < 0 || x >= this.columns)
+ return TileType.normal;
+ if (y < 0 || y >= this.rows)
+ return TileType.background;
+ return this.at(x, y).type;
+};
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/gameobjects/WaterDrop.js b/26_GamePhysics/TickTick2/gameobjects/WaterDrop.js
new file mode 100644
index 0000000..a4e2b6a
--- /dev/null
+++ b/26_GamePhysics/TickTick2/gameobjects/WaterDrop.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function WaterDrop(layer, id) {
+ powerupjs.SpriteGameObject.call(this, sprites.water, layer, id);
+ this._bounce = 0;
+}
+
+WaterDrop.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+WaterDrop.prototype.update = function (delta) {
+ this.position.y -= this._bounce;
+ var t = powerupjs.Game.totalTime * 3 + this.position.x;
+ this._bounce = Math.sin(t) * 5;
+ this.position.y += this._bounce;
+ powerupjs.SpriteGameObject.prototype.update.call(this, delta);
+};
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/gui/LevelButton.js b/26_GamePhysics/TickTick2/gui/LevelButton.js
new file mode 100644
index 0000000..45e419a
--- /dev/null
+++ b/26_GamePhysics/TickTick2/gui/LevelButton.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new powerupjs.SpriteGameObject(sprites.level_solved, ID.layer_overlays_1);
+ this._levelUnsolved = new powerupjs.SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+ this.add(this._levelUnsolved);
+
+ this._levelLocked = new powerupjs.SpriteGameObject(sprites.level_locked, ID.layer_overlays_1);
+ this.add(this._levelLocked);
+
+ var textLabel = new powerupjs.Label("Arial", "20px", ID.layer_overlays_2);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new powerupjs.Vector2(this._levelSolved.width - textLabel.width - 10, 10);
+ textLabel.color = powerupjs.Color.white;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (window.LEVELS[this.levelIndex].locked)
+ return;
+ if (powerupjs.Touch.isTouchDevice)
+ this.pressed = this.visible && powerupjs.Touch.containsTouch(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && powerupjs.Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(powerupjs.Mouse.position);
+};
+
+LevelButton.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+ var currLevel = window.LEVELS[this.levelIndex];
+ this._levelLocked.visible = currLevel.locked;
+ this._levelSolved.visible = currLevel.solved;
+ this._levelUnsolved.visible = !currLevel.solved;
+};
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/level/Level.js b/26_GamePhysics/TickTick2/level/Level.js
new file mode 100644
index 0000000..26d13b9
--- /dev/null
+++ b/26_GamePhysics/TickTick2/level/Level.js
@@ -0,0 +1,134 @@
+"use strict";
+
+function Level(levelIndex, id) {
+ powerupjs.GameObjectList.call(this, id);
+
+ this._levelIndex = levelIndex;
+ this._waterdrops = new powerupjs.GameObjectList(ID.layer_objects);
+ this._enemies = new powerupjs.GameObjectList(ID.layer_objects);
+ this._quitButton = new powerupjs.Button(sprites.button_quit, ID.layer_overlays);
+
+ this._quitButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - this._quitButton.width - 10, 10);
+ this.add(this._quitButton);
+
+ this.add(this._waterdrops);
+ this.add(this._enemies);
+
+ // backgrounds
+ var backgrounds = new powerupjs.GameObjectList(ID.layer_background);
+ var background_main = new powerupjs.SpriteGameObject(sprites.background_sky, ID.layer_background_1);
+ background_main.position = new powerupjs.Vector2(0, powerupjs.Game.size.y - background_main.height);
+ backgrounds.add(background_main);
+ this.add(backgrounds);
+
+ this.loadTiles();
+}
+
+Level.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Level.prototype.loadTiles = function () {
+ var levelData = window.LEVELS[this._levelIndex];
+
+ var hintField = new powerupjs.GameObjectList(ID.layer_overlays);
+ this.add(hintField);
+ var hintFrame = new powerupjs.SpriteGameObject(sprites.frame_hint, ID.layer_overlays_1);
+ hintField.position = new powerupjs.Vector2(hintFrame.screenCenterX, 10);
+ hintField.add(hintFrame);
+ var hintText = new powerupjs.Label("Arial", "14pt", ID.layer_overlays_2);
+ hintText.text = levelData.hint;
+ hintText.position = new powerupjs.Vector2(120, 25);
+ hintText.color = powerupjs.Color.black;
+ hintField.add(hintText);
+
+ if (powerupjs.Touch.isTouchDevice) {
+ var walkLeftButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_walkleft);
+ walkLeftButton.position = new powerupjs.Vector2(10, 500);
+ this.add(walkLeftButton);
+ var walkRightButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_walkright);
+ walkRightButton.position = new powerupjs.Vector2(walkRightButton.width + 20, 500);
+ walkRightButton.sheetIndex = 1;
+ this.add(walkRightButton);
+ var jumpButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_jump);
+ jumpButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - jumpButton.width - 10, 500);
+ jumpButton.sheetIndex = 2;
+ this.add(jumpButton);
+ }
+
+ var tiles = new TileField(levelData.tiles.length, levelData.tiles[0].length, 1, ID.tiles);
+ this.add(tiles);
+ tiles.cellWidth = 72;
+ tiles.cellHeight = 55;
+ for (var y = 0, ly = tiles.rows; y < ly; ++y)
+ for (var x = 0, lx = tiles.columns; x < lx; ++x) {
+ var t = this.loadTile(levelData.tiles[y][x], x, y);
+ tiles.add(t, x, y);
+ }
+};
+
+Level.prototype.loadTile = function (tileType, x, y) {
+ switch (tileType) {
+ case '.':
+ return new Tile();
+ case '-':
+ return this.loadBasicTile(sprites.platform, TileType.platform);
+ case '+':
+ return this.loadBasicTile(sprites.platform_hot, TileType.platform, true, false);
+ case '@':
+ return this.loadBasicTile(sprites.platform_ice, TileType.platform, false, true);
+ case 'X':
+ return this.loadEndTile(x, y);
+ case 'W':
+ return this.loadWaterTile(x, y);
+ case '1':
+ return this.loadStartTile(x, y);
+ case '#':
+ return this.loadBasicTile(sprites.wall, TileType.normal);
+ case '^':
+ return this.loadBasicTile(sprites.wall_hot, TileType.normal, true, false);
+ case '*':
+ return this.loadBasicTile(sprites.wall_ice, TileType.normal, false, true);
+ default:
+ return new Tile();
+ }
+};
+
+Level.prototype.loadBasicTile = function (id, tileType, hot, ice) {
+ var t = new Tile(id, tileType);
+ t.hot = hot;
+ t.ice = ice;
+ return t;
+};
+
+Level.prototype.loadStartTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var startPosition = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ this.add(new Player(startPosition, ID.layer_objects, ID.player));
+ return new Tile();
+};
+
+Level.prototype.loadEndTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var exit = new powerupjs.SpriteGameObject(sprites.goal, ID.layer_objects, ID.exit);
+ exit.position = new powerupjs.Vector2(x * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ exit.origin = new powerupjs.Vector2(0, exit.height);
+ this.add(exit);
+ return new Tile();
+};
+
+Level.prototype.loadWaterTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var w = new WaterDrop(ID.layer_objects);
+ w.origin = w.center.copy();
+ w.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 0.5) * tiles.cellHeight - 10);
+ this._waterdrops.add(w);
+ return new Tile();
+};
+
+Level.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ if (!this._quitButton.pressed)
+ return;
+ this.reset();
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+};
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/states/HelpState.js b/26_GamePhysics/TickTick2/states/HelpState.js
new file mode 100644
index 0000000..c71e040
--- /dev/null
+++ b/26_GamePhysics/TickTick2/states/HelpState.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function HelpState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the background
+ var background = new powerupjs.SpriteGameObject(sprites.background_help, ID.layer_background);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new powerupjs.Vector2(this.backButton.screenCenterX, 750);
+ this.add(this.backButton);
+}
+HelpState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/states/LevelMenuState.js b/26_GamePhysics/TickTick2/states/LevelMenuState.js
new file mode 100644
index 0000000..c00b239
--- /dev/null
+++ b/26_GamePhysics/TickTick2/states/LevelMenuState.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ this.background = new powerupjs.SpriteGameObject(sprites.levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new powerupjs.Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0, j = window.LEVELS.length; i < j; ++i) {
+ var row = Math.floor(i / 4);
+ var column = i % 4;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new powerupjs.Vector2(column * (level.width + 20) + 390, row * (level.height + 20) + 180);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i++) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ var playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ playingState.goToLevel(selectedLevel);
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+ }
+ else if (this.back.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/states/PlayingState.js b/26_GamePhysics/TickTick2/states/PlayingState.js
new file mode 100644
index 0000000..1b54c7c
--- /dev/null
+++ b/26_GamePhysics/TickTick2/states/PlayingState.js
@@ -0,0 +1,75 @@
+"use strict";
+
+function PlayingState() {
+ powerupjs.IGameLoopObject.call(this);
+
+ this.currentLevelIndex = -1;
+ this.levels = [];
+
+ this.loadLevelsStatus();
+ this.loadLevels();
+}
+
+PlayingState.prototype = Object.create(powerupjs.IGameLoopObject.prototype);
+
+Object.defineProperty(PlayingState.prototype, "currentLevel", {
+ get: function () {
+ return this.levels[this.currentLevelIndex];
+ }
+});
+
+PlayingState.prototype.handleInput = function (delta) {
+ this.currentLevel.handleInput(delta);
+};
+
+PlayingState.prototype.update = function (delta) {
+ this.currentLevel.update(delta);
+
+ if (this.currentLevel.gameOver)
+ powerupjs.GameStateManager.switchTo(ID.game_state_gameover);
+ else if (this.currentLevel.completed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelfinished);
+};
+
+PlayingState.prototype.draw = function () {
+ this.currentLevel.draw();
+};
+
+PlayingState.prototype.reset = function () {
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.goToLevel = function (levelIndex) {
+ if (levelIndex < 0 || levelIndex >= this.levels.length)
+ return;
+ this.currentLevelIndex = levelIndex;
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.nextLevel = function () {
+ if (this.currentLevelIndex >= window.LEVELS.length - 1)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else {
+ this.goToLevel(this.currentLevelIndex + 1);
+ window.LEVELS[this.currentLevelIndex].locked = false;
+ }
+ this.writeLevelsStatus();
+};
+
+PlayingState.prototype.loadLevels = function () {
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel += 1) {
+ this.levels.push(new Level(currLevel));
+ }
+};
+
+PlayingState.prototype.loadLevelsStatus = function () {
+ if (localStorage && localStorage.tickTickLevels) {
+ window.LEVELS = JSON.parse(localStorage.tickTickLevels);
+ }
+};
+
+PlayingState.prototype.writeLevelsStatus = function () {
+ if (!localStorage)
+ return;
+ localStorage.tickTickLevels = JSON.stringify(window.LEVELS);
+};
\ No newline at end of file
diff --git a/26_GamePhysics/TickTick2/states/TitleMenuState.js b/26_GamePhysics/TickTick2/states/TitleMenuState.js
new file mode 100644
index 0000000..0628cb6
--- /dev/null
+++ b/26_GamePhysics/TickTick2/states/TitleMenuState.js
@@ -0,0 +1,29 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the title screen
+ var titleScreen = new powerupjs.SpriteGameObject(sprites.background_title, ID.layer_background);
+ this.add(titleScreen);
+
+ // add a play button
+ this.playButton = new powerupjs.Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new powerupjs.Vector2(this.playButton.screenCenterX, 540);
+ this.add(this.playButton);
+
+ // add a help button
+ this.helpButton = new powerupjs.Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new powerupjs.Vector2(this.helpButton.screenCenterX, 600);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_help);
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/LAB.min.js b/27_IntelligentEnemies/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/27_IntelligentEnemies/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/TickTick.html b/27_IntelligentEnemies/TickTick3/TickTick.html
new file mode 100644
index 0000000..3691cd9
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/TickTick.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+ TickTick
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/TickTick.js b/27_IntelligentEnemies/TickTick3/TickTick.js
new file mode 100644
index 0000000..84cfafe
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/TickTick.js
@@ -0,0 +1,113 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+powerupjs.Game.loadAssets = function () {
+ var loadSprite = function (sprite, collisionMask) {
+ return new powerupjs.SpriteSheet("../../assets/TickTick/sprites/" + sprite/*, collisionMask*/);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new powerupjs.Sound("../../assets/TickTick/sounds/" + sound, looping);
+ };
+
+ sprites.background_title = loadSprite("backgrounds/spr_title.jpg");
+ sprites.background_help = loadSprite("backgrounds/spr_help.jpg");
+ sprites.background_sky = loadSprite("backgrounds/spr_sky.jpg");
+ sprites.cloud_1 = loadSprite("backgrounds/spr_cloud_1.png");
+ sprites.cloud_2 = loadSprite("backgrounds/spr_cloud_2.png");
+ sprites.cloud_3 = loadSprite("backgrounds/spr_cloud_3.png");
+ sprites.cloud_4 = loadSprite("backgrounds/spr_cloud_4.png");
+ sprites.cloud_5 = loadSprite("backgrounds/spr_cloud_5.png");
+ sprites.mountain_1 = loadSprite("backgrounds/spr_mountain_1.png");
+ sprites.mountain_2 = loadSprite("backgrounds/spr_mountain_2.png");
+ sprites.levelselect = loadSprite("backgrounds/spr_levelselect.jpg");
+
+ if (powerupjs.Touch.isTouchDevice) {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_tap.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_tap.png");
+ } else {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_click.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_click.png");
+ }
+ sprites.frame_hint = loadSprite("overlays/spr_frame_hint.png");
+ sprites.timer = loadSprite("overlays/spr_timer.png");
+
+ sprites.button_play = loadSprite("gui/spr_button_play.png");
+ sprites.button_help = loadSprite("gui/spr_button_help.png");
+ sprites.button_back = loadSprite("gui/spr_button_back.png");
+ sprites.button_quit = loadSprite("gui/spr_button_quit.png");
+ sprites.level_solved = loadSprite("gui/spr_level_solved.png");
+ sprites.level_unsolved = loadSprite("gui/spr_level_unsolved.png");
+ sprites.level_locked = loadSprite("gui/spr_level_locked.png");
+ sprites.buttons_player = loadSprite("gui/spr_buttons_player@3.png");
+
+ sprites.wall = loadSprite("tiles/spr_wall.png");
+ sprites.wall_hot = loadSprite("tiles/spr_wall_hot.png");
+ sprites.wall_ice = loadSprite("tiles/spr_wall_ice.png");
+ sprites.platform = loadSprite("tiles/spr_platform.png");
+ sprites.platform_hot = loadSprite("tiles/spr_platform_hot.png");
+ sprites.platform_ice = loadSprite("tiles/spr_platform_ice.png");
+
+ sprites.goal = loadSprite("spr_goal.png", true);
+ sprites.water = loadSprite("spr_water.png");
+
+ // player animations
+ sprites.player_idle = loadSprite("player/spr_idle.png", true);
+ sprites.player_run = loadSprite("player/spr_run@13.png", true);
+ sprites.player_jump = loadSprite("player/spr_jump@14.png", true);
+ sprites.player_celebrate = loadSprite("player/spr_celebrate@14.png");
+ sprites.player_die = loadSprite("player/spr_die@5.png");
+ sprites.player_explode = loadSprite("player/spr_explode@5x5.png");
+
+ // enemy animations
+ sprites.rocket = loadSprite("rocket/spr_rocket@3.png", true);
+ sprites.flame = loadSprite("flame/spr_flame@9.png", true);
+ sprites.turtle_sneeze = loadSprite("turtle/spr_sneeze@9.png", true);
+ sprites.turtle_idle = loadSprite("turtle/spr_idle.png", true);
+ sprites.sparky_electrocute = loadSprite("sparky/spr_electrocute@6x5.png", true);
+ sprites.sparky_idle = loadSprite("sparky/spr_idle.png", true);
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.player_die = loadSound("snd_player_die");
+ sounds.player_explode = loadSound("snd_player_explode");
+ sounds.player_fall = loadSound("snd_player_fall");
+ sounds.player_jump = loadSound("snd_player_jump");
+ sounds.player_won = loadSound("snd_player_won");
+ sounds.water_collected = loadSound("snd_water_collected");
+};
+
+powerupjs.Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.player = 1;
+ ID.timer = 2;
+ ID.tiles = 3;
+ ID.exit = 4;
+ ID.hint_timer = 5;
+ ID.button_walkleft = 6;
+ ID.button_walkright = 7;
+ ID.button_jump = 8;
+
+ ID.game_state_title = powerupjs.GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = powerupjs.GameStateManager.add(new HelpState());
+ ID.game_state_playing = powerupjs.GameStateManager.add(new PlayingState());
+ ID.game_state_levelselect = powerupjs.GameStateManager.add(new LevelMenuState());
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/game-layout.css b/27_IntelligentEnemies/TickTick3/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/Player.js b/27_IntelligentEnemies/TickTick3/gameobjects/Player.js
new file mode 100644
index 0000000..e9666c3
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/Player.js
@@ -0,0 +1,144 @@
+"use strict";
+
+function Player(start, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+ this._startPosition = start;
+ this._previousYPosition = 0;
+
+ this.onTheGround = false;
+ this.alive = true;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+ this.finished = false;
+ this.exploded = false;
+
+ this.loadAnimation(sprites.player_idle, "idle", true);
+ this.loadAnimation(sprites.player_run, "run", true, 0.05);
+ this.loadAnimation(sprites.player_jump, "jump", false, 0.05);
+ this.loadAnimation(sprites.player_celebrate, "celebrate", false, 0.05);
+ this.loadAnimation(sprites.player_die, "die", false);
+ this.loadAnimation(sprites.player_explode, "explode", false, 0.04);
+
+ this.reset();
+}
+
+Player.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Player.prototype.reset = function () {
+ this.playAnimation("idle");
+
+ this._previousYPosition = this.boundingBox.bottom;
+ this.position = this._startPosition.copy();
+ this.velocity = powerupjs.Vector2.zero;
+
+ this.onTheGround = true;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+};
+
+Player.prototype.handleInput = function (delta) {
+ var walkingSpeed = 400;
+ if (this.walkingOnIce) {
+ walkingSpeed *= 1.5;
+ this.velocity.x = Math.sign(this.velocity.x) * walkingSpeed;
+ } else if (this.onTheGround)
+ this.velocity.x = 0;
+
+ if (powerupjs.Touch.isTouchDevice) {
+ // get the buttons
+ var walkLeftButton = this.root.find(ID.button_walkleft);
+ var walkRightButton = this.root.find(ID.button_walkright);
+ if (walkLeftButton.down)
+ this.velocity.x = -walkingSpeed;
+ else if (walkRightButton.down)
+ this.velocity.x = walkingSpeed;
+ } else {
+ if (powerupjs.Keyboard.down(powerupjs.Keys.left))
+ this.velocity.x = -walkingSpeed;
+ else if (powerupjs.Keyboard.down(powerupjs.Keys.right))
+ this.velocity.x = walkingSpeed;
+ }
+
+ if (this.velocity.x != 0)
+ this.mirror = this.velocity.x < 0;
+
+ if (powerupjs.Touch.isTouchDevice) {
+ var jumpButton = this.root.find(ID.button_jump);
+ if (jumpButton.pressed && this.onTheGround)
+ this.jump();
+ } else {
+ if (powerupjs.Keyboard.pressed(powerupjs.Keys.space) && this.onTheGround)
+ this.jump();
+ }
+};
+
+Player.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ this.doPhysics();
+ if (!this.alive || this.finished)
+ return;
+
+ if (this.onTheGround)
+ if (this.velocity.x == 0)
+ this.playAnimation("idle");
+ else
+ this.playAnimation("run");
+ else if (this.velocity.y < 0)
+ this.playAnimation("jump");
+};
+
+Player.prototype.jump = function (speed) {
+ sounds.player_jump.play();
+ speed = typeof speed !== 'undefined' ? speed : 1100;
+ this.velocity.y = -speed;
+};
+
+Player.prototype.doPhysics = function () {
+ this.velocity.y += 55;
+ this.handleCollisions();
+};
+
+Player.prototype.handleCollisions = function () {
+ this.onTheGround = false;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+
+ var tiles = this.root.find(ID.tiles);
+
+ var x_floor = Math.floor(this.position.x / tiles.cellWidth);
+ var y_floor = Math.floor(this.position.y / tiles.cellHeight);
+
+ for (var y = y_floor - 2; y <= y_floor + 1; ++y)
+ for (var x = x_floor - 1; x <= x_floor + 1; ++x) {
+ var tileType = tiles.getTileType(x, y);
+ if (tileType === TileType.background)
+ continue;
+ var tileBounds = new powerupjs.Rectangle(x * tiles.cellWidth, y * tiles.cellHeight,
+ tiles.cellWidth, tiles.cellHeight);
+ var boundingBox = this.boundingBox;
+ boundingBox.height += 1;
+ if (!tileBounds.intersects(boundingBox))
+ continue;
+ var depth = boundingBox.calculateIntersectionDepth(tileBounds);
+ if (Math.abs(depth.x) < Math.abs(depth.y)) {
+ if (tileType === TileType.normal)
+ this.position.x += depth.x;
+ continue;
+ }
+ if (this._previousYPosition <= tileBounds.top && tileType !== TileType.background) {
+ this.onTheGround = true;
+ this.velocity.y = 0;
+ var currentTile = tiles.at(x, y);
+ if (currentTile !== null) {
+ this.walkingOnIce = this.walkingOnIce || currentTile.ice;
+ this.walkingOnHot = this.walkingOnHot || currentTile.hot;
+ }
+ }
+ if (tileType === TileType.normal || this.onTheGround)
+ this.position.y += depth.y + 1;
+ }
+ this._previousYPosition = this.position.y;
+};
+
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/Tile.js b/27_IntelligentEnemies/TickTick3/gameobjects/Tile.js
new file mode 100644
index 0000000..19a1c2e
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/Tile.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var TileType = {
+ background: 0,
+ normal: 1,
+ platform: 2
+};
+
+function Tile(sprite, tileTp, layer, id) {
+ sprite = typeof sprite !== 'undefined' ? sprite : sprites.wall;
+ powerupjs.SpriteGameObject.call(this, sprite, layer, id);
+
+ this.hot = false;
+ this.ice = false;
+ this.type = typeof tileTp !== 'undefined' ? tileTp : TileType.background;
+}
+
+Tile.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Tile.prototype.draw = function () {
+ if (this.type == TileType.background)
+ return;
+ powerupjs.SpriteGameObject.prototype.draw.call(this);
+};
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/TileField.js b/27_IntelligentEnemies/TickTick3/gameobjects/TileField.js
new file mode 100644
index 0000000..83b0fe1
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/TileField.js
@@ -0,0 +1,15 @@
+"use strict";
+
+function TileField(rows, columns, layer, id) {
+ powerupjs.GameObjectGrid.call(this, rows, columns, layer, id);
+}
+
+TileField.prototype = Object.create(powerupjs.GameObjectGrid.prototype);
+
+TileField.prototype.getTileType = function (x, y) {
+ if (x < 0 || x >= this.columns)
+ return TileType.normal;
+ if (y < 0 || y >= this.rows)
+ return TileType.background;
+ return this.at(x, y).type;
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/WaterDrop.js b/27_IntelligentEnemies/TickTick3/gameobjects/WaterDrop.js
new file mode 100644
index 0000000..a4e2b6a
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/WaterDrop.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function WaterDrop(layer, id) {
+ powerupjs.SpriteGameObject.call(this, sprites.water, layer, id);
+ this._bounce = 0;
+}
+
+WaterDrop.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+WaterDrop.prototype.update = function (delta) {
+ this.position.y -= this._bounce;
+ var t = powerupjs.Game.totalTime * 3 + this.position.x;
+ this._bounce = Math.sin(t) * 5;
+ this.position.y += this._bounce;
+ powerupjs.SpriteGameObject.prototype.update.call(this, delta);
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/enemies/PatrollingEnemy.js b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/PatrollingEnemy.js
new file mode 100644
index 0000000..c1f8ee6
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/PatrollingEnemy.js
@@ -0,0 +1,42 @@
+"use strict";
+
+function PatrollingEnemy(layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this._waitTime = 0;
+ this.velocity.x = 120;
+
+ this.loadAnimation(sprites.flame, "default", true);
+ this.playAnimation("default");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+}
+PatrollingEnemy.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+PatrollingEnemy.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this._waitTime > 0) {
+ this._waitTime -= delta;
+ if (this._waitTime <= 0)
+ this.turnAround();
+ }
+ else {
+ var tiles = this.root.find(ID.tiles);
+ var posX = this.boundingBox.left;
+ if (!this.mirror)
+ posX = this.boundingBox.right;
+ var tileX = Math.floor(posX / tiles.cellWidth);
+ var tileY = Math.floor(this.position.y / tiles.cellHeight);
+ if (tiles.getTileType(tileX, tileY - 1) === TileType.normal ||
+ tiles.getTileType(tileX, tileY) === TileType.background) {
+ this._waitTime = 0.5;
+ this.velocity.x = 0;
+ }
+ }
+};
+
+PatrollingEnemy.prototype.turnAround = function () {
+ this.mirror = !this.mirror;
+ this.velocity.x = 120;
+ if (this.mirror)
+ this.velocity.x = -this.velocity.x;
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/enemies/PlayerFollowingEnemy.js b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/PlayerFollowingEnemy.js
new file mode 100644
index 0000000..f2eb88d
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/PlayerFollowingEnemy.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function PlayerFollowingEnemy(layer, id) {
+ PatrollingEnemy.call(this, layer, id);
+}
+
+PlayerFollowingEnemy.prototype = Object.create(PatrollingEnemy.prototype);
+
+PlayerFollowingEnemy.prototype.update = function (delta) {
+ PatrollingEnemy.prototype.update.call(this, delta);
+
+ var player = this.root.find(ID.player);
+ var direction = player.position.x - this.position.x;
+ if (Math.sign(direction) !== Math.sign(this.velocity.x) && player.velocity.x !== 0
+ && this.velocity.x !== 0)
+ this.turnAround();
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Rocket.js b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Rocket.js
new file mode 100644
index 0000000..8b6bfb2
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Rocket.js
@@ -0,0 +1,41 @@
+"use strict";
+
+function Rocket(moveToLeft, startPosition, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.spawnTime = 0;
+ this.startPosition = startPosition;
+ this.mirror = moveToLeft;
+
+ this.loadAnimation(sprites.rocket, "default", true, 0.5);
+ this.playAnimation("default");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+
+ this.reset();
+}
+
+Rocket.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Rocket.prototype.reset = function () {
+ this.visible = false;
+ this.position = this.startPosition.copy();
+ this.velocity = powerupjs.Vector2.zero;
+ this.spawnTime = Math.random() * 5;
+};
+
+Rocket.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.spawnTime > 0) {
+ this.spawnTime -= delta;
+ return;
+ }
+ this.visible = true;
+ this.velocity.x = 600;
+ if (this.mirror)
+ this.velocity.x *= -1;
+ // check if we are far enough outside the screen
+ var screenBox = new powerupjs.Rectangle(-200, -200, powerupjs.Game.size.x + 400,
+ powerupjs.Game.size.y + 400);
+ if (!screenBox.intersects(this.boundingBox))
+ this.reset();
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Sparky.js b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Sparky.js
new file mode 100644
index 0000000..235ec18
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Sparky.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function Sparky(initialY, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.idleTime = 0;
+ this.yoffset = 0;
+ this.initialY = initialY;
+
+ this.loadAnimation(sprites.sparky_electrocute, "electrocute", false);
+ this.loadAnimation(sprites.sparky_idle, "idle", true);
+ this.playAnimation("idle");
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+ this.reset();
+}
+
+Sparky.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Sparky.prototype.reset = function () {
+ this.idleTime = Math.random() * 5;
+ this.position.y = this.initialY;
+ this.yoffset = 120;
+ this.velocity = powerupjs.Vector2.zero;
+};
+
+Sparky.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.idleTime <= 0) {
+ this.playAnimation("electrocute");
+ if (this.velocity.y != 0) {
+ // falling down or going up
+ this.yoffset -= this.velocity.y * delta;
+ if (this.yoffset <= 0)
+ this.velocity.y = 0;
+ else if (this.yoffset >= 120)
+ this.reset();
+ }
+ else if (this.animationEnded())
+ this.velocity.y = -60;
+ }
+ else {
+ this.playAnimation("idle");
+ this.idleTime -= delta;
+ if (this.idleTime <= 0)
+ this.velocity.y = 300;
+ }
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Turtle.js b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Turtle.js
new file mode 100644
index 0000000..d3a0d6c
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/Turtle.js
@@ -0,0 +1,37 @@
+"use strict";
+
+function Turtle(layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.waitTime = 0;
+ this.sneezing = false;
+
+ this.loadAnimation(sprites.turtle_sneeze, "sneeze", false);
+ this.loadAnimation(sprites.turtle_idle, "idle", true);
+ this.playAnimation("idle");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+
+ this.reset();
+}
+
+Turtle.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Turtle.prototype.reset = function () {
+ this.waitTime = 5;
+ this.sneezing = false;
+};
+
+Turtle.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.sneezing)
+ this.playAnimation("sneeze");
+ else
+ this.playAnimation("idle");
+
+ if (this.waitTime > 0)
+ this.waitTime -= delta;
+ else {
+ this.sneezing = !this.sneezing;
+ this.waitTime = 5;
+ }
+};
diff --git a/27_IntelligentEnemies/TickTick3/gameobjects/enemies/UnpredictableEnemy.js b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/UnpredictableEnemy.js
new file mode 100644
index 0000000..cf009ed
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gameobjects/enemies/UnpredictableEnemy.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function UnpredictableEnemy(layer, id) {
+ PatrollingEnemy.call(this, layer, id);
+}
+
+UnpredictableEnemy.prototype = Object.create(PatrollingEnemy.prototype);
+
+UnpredictableEnemy.prototype.update = function (delta) {
+ PatrollingEnemy.prototype.update.call(this, delta);
+ if (this._waitTime <= 0 && Math.random() < 0.01) {
+ this.turnAround();
+ this.velocity.x = Math.sign(this.velocity.x) *
+ Math.random() * 300;
+ }
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/gui/LevelButton.js b/27_IntelligentEnemies/TickTick3/gui/LevelButton.js
new file mode 100644
index 0000000..45e419a
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/gui/LevelButton.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new powerupjs.SpriteGameObject(sprites.level_solved, ID.layer_overlays_1);
+ this._levelUnsolved = new powerupjs.SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+ this.add(this._levelUnsolved);
+
+ this._levelLocked = new powerupjs.SpriteGameObject(sprites.level_locked, ID.layer_overlays_1);
+ this.add(this._levelLocked);
+
+ var textLabel = new powerupjs.Label("Arial", "20px", ID.layer_overlays_2);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new powerupjs.Vector2(this._levelSolved.width - textLabel.width - 10, 10);
+ textLabel.color = powerupjs.Color.white;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (window.LEVELS[this.levelIndex].locked)
+ return;
+ if (powerupjs.Touch.isTouchDevice)
+ this.pressed = this.visible && powerupjs.Touch.containsTouch(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && powerupjs.Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(powerupjs.Mouse.position);
+};
+
+LevelButton.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+ var currLevel = window.LEVELS[this.levelIndex];
+ this._levelLocked.visible = currLevel.locked;
+ this._levelSolved.visible = currLevel.solved;
+ this._levelUnsolved.visible = !currLevel.solved;
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/level/Level.js b/27_IntelligentEnemies/TickTick3/level/Level.js
new file mode 100644
index 0000000..c113764
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/level/Level.js
@@ -0,0 +1,192 @@
+"use strict";
+
+function Level(levelIndex, id) {
+ powerupjs.GameObjectList.call(this, id);
+
+ this._levelIndex = levelIndex;
+ this._waterdrops = new powerupjs.GameObjectList(ID.layer_objects);
+ this._enemies = new powerupjs.GameObjectList(ID.layer_objects);
+ this._quitButton = new powerupjs.Button(sprites.button_quit, ID.layer_overlays);
+
+ this._quitButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - this._quitButton.width - 10, 10);
+ this.add(this._quitButton);
+
+ this.add(this._waterdrops);
+ this.add(this._enemies);
+
+ // backgrounds
+ var backgrounds = new powerupjs.GameObjectList(ID.layer_background);
+ var background_main = new powerupjs.SpriteGameObject(sprites.background_sky, ID.layer_background_1);
+ background_main.position = new powerupjs.Vector2(0, powerupjs.Game.size.y - background_main.height);
+ backgrounds.add(background_main);
+
+ this.add(backgrounds);
+
+ this.loadTiles();
+}
+
+Level.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Level.prototype.loadTiles = function () {
+ var levelData = window.LEVELS[this._levelIndex];
+
+ var hintField = new powerupjs.GameObjectList(ID.layer_overlays);
+ this.add(hintField);
+ var hintFrame = new powerupjs.SpriteGameObject(sprites.frame_hint, ID.layer_overlays_1);
+ hintField.position = new powerupjs.Vector2(hintFrame.screenCenterX, 10);
+ hintField.add(hintFrame);
+ var hintText = new powerupjs.Label("Arial", "14pt", ID.layer_overlays_2);
+ hintText.text = levelData.hint;
+ hintText.position = new powerupjs.Vector2(120, 25);
+ hintText.color = powerupjs.Color.black;
+ hintField.add(hintText);
+
+ if (powerupjs.Touch.isTouchDevice) {
+ var walkLeftButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_walkleft);
+ walkLeftButton.position = new powerupjs.Vector2(10, 500);
+ this.add(walkLeftButton);
+ var walkRightButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_walkright);
+ walkRightButton.position = new powerupjs.Vector2(walkRightButton.width + 20, 500);
+ walkRightButton.sheetIndex = 1;
+ this.add(walkRightButton);
+ var jumpButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_jump);
+ jumpButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - jumpButton.width - 10, 500);
+ jumpButton.sheetIndex = 2;
+ this.add(jumpButton);
+ }
+
+ var tiles = new TileField(levelData.tiles.length, levelData.tiles[0].length, 1, ID.tiles);
+ this.add(tiles);
+ tiles.cellWidth = 72;
+ tiles.cellHeight = 55;
+ for (var y = 0, ly = tiles.rows; y < ly; ++y)
+ for (var x = 0, lx = tiles.columns; x < lx; ++x) {
+ var t = this.loadTile(levelData.tiles[y][x], x, y);
+ tiles.add(t, x, y);
+ }
+};
+
+Level.prototype.loadTile = function (tileType, x, y) {
+ switch (tileType) {
+ case '.':
+ return new Tile();
+ case '-':
+ return this.loadBasicTile(sprites.platform, TileType.platform);
+ case '+':
+ return this.loadBasicTile(sprites.platform_hot, TileType.platform, true, false);
+ case '@':
+ return this.loadBasicTile(sprites.platform_ice, TileType.platform, false, true);
+ case 'X':
+ return this.loadEndTile(x, y);
+ case 'W':
+ return this.loadWaterTile(x, y);
+ case '1':
+ return this.loadStartTile(x, y);
+ case '#':
+ return this.loadBasicTile(sprites.wall, TileType.normal);
+ case '^':
+ return this.loadBasicTile(sprites.wall_hot, TileType.normal, true, false);
+ case '*':
+ return this.loadBasicTile(sprites.wall_ice, TileType.normal, false, true);
+ case 'R':
+ return this.loadRocketTile(x, y, true);
+ case 'r':
+ return this.loadRocketTile(x, y, false);
+ case 'S':
+ return this.loadSparkyTile(x, y);
+ case 'T':
+ return this.loadTurtleTile(x, y);
+ case 'A':
+ case 'B':
+ case 'C':
+ return this.loadFlameTile(x, y, tileType);
+
+ default:
+ return new Tile();
+ }
+};
+
+Level.prototype.loadBasicTile = function (id, tileType, hot, ice) {
+ var t = new Tile(id, tileType);
+ t.hot = hot;
+ t.ice = ice;
+ return t;
+};
+
+Level.prototype.loadStartTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var startPosition = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ this.add(new Player(startPosition, ID.layer_objects, ID.player));
+ return new Tile();
+};
+
+Level.prototype.loadEndTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var exit = new powerupjs.SpriteGameObject(sprites.goal, ID.layer_objects, ID.exit);
+ exit.position = new powerupjs.Vector2(x * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ exit.origin = new powerupjs.Vector2(0, exit.height);
+ this.add(exit);
+ return new Tile();
+};
+
+Level.prototype.loadWaterTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var w = new WaterDrop(ID.layer_objects);
+ w.origin = w.center.copy();
+ w.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 0.5) * tiles.cellHeight - 10);
+ this._waterdrops.add(w);
+ return new Tile();
+};
+
+Level.prototype.loadRocketTile = function (x, y, moveToLeft) {
+ var tiles = this.find(ID.tiles);
+ var startPosition = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ var enemy = new Rocket(moveToLeft, startPosition, ID.layer_objects);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadTurtleTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var enemy = new Turtle(ID.layer_objects);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight + 25);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadSparkyTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var enemy = new Sparky((y + 1) * tiles.cellHeight - 100, ID.layer_objects);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight - 100);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadFlameTile = function (x, y, enemyType) {
+ var enemy = null;
+ switch (enemyType) {
+ case 'A':
+ enemy = new UnpredictableEnemy(ID.layer_objects);
+ break;
+ case 'B':
+ enemy = new PlayerFollowingEnemy(ID.layer_objects);
+ break;
+ case 'C':
+ default:
+ enemy = new PatrollingEnemy(ID.layer_objects);
+ break;
+ }
+ var tiles = this.find(ID.tiles);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ if (!this._quitButton.pressed)
+ return;
+ this.reset();
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/states/HelpState.js b/27_IntelligentEnemies/TickTick3/states/HelpState.js
new file mode 100644
index 0000000..c71e040
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/states/HelpState.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function HelpState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the background
+ var background = new powerupjs.SpriteGameObject(sprites.background_help, ID.layer_background);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new powerupjs.Vector2(this.backButton.screenCenterX, 750);
+ this.add(this.backButton);
+}
+HelpState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/states/LevelMenuState.js b/27_IntelligentEnemies/TickTick3/states/LevelMenuState.js
new file mode 100644
index 0000000..c00b239
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/states/LevelMenuState.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ this.background = new powerupjs.SpriteGameObject(sprites.levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new powerupjs.Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0, j = window.LEVELS.length; i < j; ++i) {
+ var row = Math.floor(i / 4);
+ var column = i % 4;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new powerupjs.Vector2(column * (level.width + 20) + 390, row * (level.height + 20) + 180);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i++) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ var playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ playingState.goToLevel(selectedLevel);
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+ }
+ else if (this.back.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/states/PlayingState.js b/27_IntelligentEnemies/TickTick3/states/PlayingState.js
new file mode 100644
index 0000000..383b06d
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/states/PlayingState.js
@@ -0,0 +1,83 @@
+"use strict";
+
+function PlayingState() {
+ powerupjs.IGameLoopObject.call(this);
+
+ this.currentLevelIndex = -1;
+ this.levels = [];
+
+ this.loadLevelsStatus();
+ this.loadLevels();
+
+ //--- CODE ADDED FOR TESTING ---
+ // In the lines below, I set all levels to unlocked so you can test the additions to the game.
+ // In the final version of the game (Ch 29), these lines are removed again
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel += 1) {
+ window.LEVELS[currLevel].locked = false;
+ }
+ //--- END CODE ADDED FOR TESTING ---
+}
+
+PlayingState.prototype = Object.create(powerupjs.IGameLoopObject.prototype);
+
+Object.defineProperty(PlayingState.prototype, "currentLevel", {
+ get: function () {
+ return this.levels[this.currentLevelIndex];
+ }
+});
+
+PlayingState.prototype.handleInput = function (delta) {
+ this.currentLevel.handleInput(delta);
+};
+
+PlayingState.prototype.update = function (delta) {
+ this.currentLevel.update(delta);
+
+ if (this.currentLevel.gameOver)
+ powerupjs.GameStateManager.switchTo(ID.game_state_gameover);
+ else if (this.currentLevel.completed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelfinished);
+};
+
+PlayingState.prototype.draw = function () {
+ this.currentLevel.draw();
+};
+
+PlayingState.prototype.reset = function () {
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.goToLevel = function (levelIndex) {
+ if (levelIndex < 0 || levelIndex >= this.levels.length)
+ return;
+ this.currentLevelIndex = levelIndex;
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.nextLevel = function () {
+ if (this.currentLevelIndex >= window.LEVELS.length - 1)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else {
+ this.goToLevel(this.currentLevelIndex + 1);
+ window.LEVELS[this.currentLevelIndex].locked = false;
+ }
+ this.writeLevelsStatus();
+};
+
+PlayingState.prototype.loadLevels = function () {
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel += 1) {
+ this.levels.push(new Level(currLevel));
+ }
+};
+
+PlayingState.prototype.loadLevelsStatus = function () {
+ if (localStorage && localStorage.tickTickLevels) {
+ window.LEVELS = JSON.parse(localStorage.tickTickLevels);
+ }
+};
+
+PlayingState.prototype.writeLevelsStatus = function () {
+ if (!localStorage)
+ return;
+ localStorage.tickTickLevels = JSON.stringify(window.LEVELS);
+};
\ No newline at end of file
diff --git a/27_IntelligentEnemies/TickTick3/states/TitleMenuState.js b/27_IntelligentEnemies/TickTick3/states/TitleMenuState.js
new file mode 100644
index 0000000..0628cb6
--- /dev/null
+++ b/27_IntelligentEnemies/TickTick3/states/TitleMenuState.js
@@ -0,0 +1,29 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the title screen
+ var titleScreen = new powerupjs.SpriteGameObject(sprites.background_title, ID.layer_background);
+ this.add(titleScreen);
+
+ // add a play button
+ this.playButton = new powerupjs.Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new powerupjs.Vector2(this.playButton.screenCenterX, 540);
+ this.add(this.playButton);
+
+ // add a help button
+ this.helpButton = new powerupjs.Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new powerupjs.Vector2(this.helpButton.screenCenterX, 600);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_help);
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/LAB.min.js b/28_PlayerInteraction/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/28_PlayerInteraction/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/TickTick.html b/28_PlayerInteraction/TickTick4/TickTick.html
new file mode 100644
index 0000000..3691cd9
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/TickTick.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+ TickTick
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/TickTick.js b/28_PlayerInteraction/TickTick4/TickTick.js
new file mode 100644
index 0000000..8b490b7
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/TickTick.js
@@ -0,0 +1,113 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+powerupjs.Game.loadAssets = function () {
+ var loadSprite = function (sprite, collisionMask) {
+ return new powerupjs.SpriteSheet("../../assets/TickTick/sprites/" + sprite/*, collisionMask*/);
+ }
+
+ var loadSound = function (sound, looping) {
+ return new powerupjs.Sound("../../assets/TickTick/sounds/" + sound, looping);
+ };
+
+ sprites.background_title = loadSprite("backgrounds/spr_title.jpg");
+ sprites.background_help = loadSprite("backgrounds/spr_help.jpg");
+ sprites.background_sky = loadSprite("backgrounds/spr_sky.jpg");
+ sprites.cloud_1 = loadSprite("backgrounds/spr_cloud_1.png");
+ sprites.cloud_2 = loadSprite("backgrounds/spr_cloud_2.png");
+ sprites.cloud_3 = loadSprite("backgrounds/spr_cloud_3.png");
+ sprites.cloud_4 = loadSprite("backgrounds/spr_cloud_4.png");
+ sprites.cloud_5 = loadSprite("backgrounds/spr_cloud_5.png");
+ sprites.mountain_1 = loadSprite("backgrounds/spr_mountain_1.png");
+ sprites.mountain_2 = loadSprite("backgrounds/spr_mountain_2.png");
+ sprites.levelselect = loadSprite("backgrounds/spr_levelselect.jpg");
+
+ if (powerupjs.Touch.isTouchDevice) {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_tap.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_tap.png");
+ } else {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_click.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_click.png");
+ }
+ sprites.frame_hint = loadSprite("overlays/spr_frame_hint.png");
+ sprites.timer = loadSprite("overlays/spr_timer.png");
+
+ sprites.button_play = loadSprite("gui/spr_button_play.png");
+ sprites.button_help = loadSprite("gui/spr_button_help.png");
+ sprites.button_back = loadSprite("gui/spr_button_back.png");
+ sprites.button_quit = loadSprite("gui/spr_button_quit.png");
+ sprites.level_solved = loadSprite("gui/spr_level_solved.png");
+ sprites.level_unsolved = loadSprite("gui/spr_level_unsolved.png");
+ sprites.level_locked = loadSprite("gui/spr_level_locked.png");
+ sprites.buttons_player = loadSprite("gui/spr_buttons_player@3.png");
+
+ sprites.wall = loadSprite("tiles/spr_wall.png");
+ sprites.wall_hot = loadSprite("tiles/spr_wall_hot.png");
+ sprites.wall_ice = loadSprite("tiles/spr_wall_ice.png");
+ sprites.platform = loadSprite("tiles/spr_platform.png");
+ sprites.platform_hot = loadSprite("tiles/spr_platform_hot.png");
+ sprites.platform_ice = loadSprite("tiles/spr_platform_ice.png");
+
+ sprites.goal = loadSprite("spr_goal.png", true);
+ sprites.water = loadSprite("spr_water.png");
+
+ // player animations
+ sprites.player_idle = loadSprite("player/spr_idle.png", true);
+ sprites.player_run = loadSprite("player/spr_run@13.png", true);
+ sprites.player_jump = loadSprite("player/spr_jump@14.png", true);
+ sprites.player_celebrate = loadSprite("player/spr_celebrate@14.png");
+ sprites.player_die = loadSprite("player/spr_die@5.png");
+ sprites.player_explode = loadSprite("player/spr_explode@5x5.png");
+
+ // enemy animations
+ sprites.rocket = loadSprite("rocket/spr_rocket@3.png", true);
+ sprites.flame = loadSprite("flame/spr_flame@9.png", true);
+ sprites.turtle_sneeze = loadSprite("turtle/spr_sneeze@9.png", true);
+ sprites.turtle_idle = loadSprite("turtle/spr_idle.png", true);
+ sprites.sparky_electrocute = loadSprite("sparky/spr_electrocute@6x5.png", true);
+ sprites.sparky_idle = loadSprite("sparky/spr_idle.png", true);
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.player_die = loadSound("snd_player_die");
+ sounds.player_explode = loadSound("snd_player_explode");
+ sounds.player_fall = loadSound("snd_player_fall");
+ sounds.player_jump = loadSound("snd_player_jump");
+ sounds.player_won = loadSound("snd_player_won");
+ sounds.water_collected = loadSound("snd_water_collected");
+};
+
+powerupjs.Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.player = 1;
+ ID.timer = 2;
+ ID.tiles = 3;
+ ID.exit = 4;
+ ID.hint_timer = 5;
+ ID.button_walkleft = 6;
+ ID.button_walkright = 7;
+ ID.button_jump = 8;
+
+ ID.game_state_title = powerupjs.GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = powerupjs.GameStateManager.add(new HelpState());
+ ID.game_state_playing = powerupjs.GameStateManager.add(new PlayingState());
+ ID.game_state_levelselect = powerupjs.GameStateManager.add(new LevelMenuState());
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/game-layout.css b/28_PlayerInteraction/TickTick4/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/Player.js b/28_PlayerInteraction/TickTick4/gameobjects/Player.js
new file mode 100644
index 0000000..c290e37
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/Player.js
@@ -0,0 +1,166 @@
+"use strict";
+
+function Player(start, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+ this._startPosition = start;
+ this._previousYPosition = 0;
+
+ this.onTheGround = false;
+ this.alive = true;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+ this.finished = false;
+ this.exploded = false;
+
+ this.loadAnimation(sprites.player_idle, "idle", true);
+ this.loadAnimation(sprites.player_run, "run", true, 0.05);
+ this.loadAnimation(sprites.player_jump, "jump", false, 0.05);
+ this.loadAnimation(sprites.player_celebrate, "celebrate", false, 0.05);
+ this.loadAnimation(sprites.player_die, "die", false);
+ this.loadAnimation(sprites.player_explode, "explode", false, 0.04);
+
+ this.reset();
+}
+
+Player.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Player.prototype.reset = function () {
+ this.playAnimation("idle");
+
+ this._previousYPosition = this.boundingBox.bottom;
+ this.position = this._startPosition.copy();
+ this.velocity = powerupjs.Vector2.zero;
+
+ this.onTheGround = true;
+ this.alive = true;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+};
+
+Player.prototype.handleInput = function (delta) {
+ if (!this.alive)
+ return;
+ var walkingSpeed = 400;
+ if (this.walkingOnIce) {
+ walkingSpeed *= 1.5;
+ this.velocity.x = Math.sign(this.velocity.x) * walkingSpeed;
+ } else if (this.onTheGround)
+ this.velocity.x = 0;
+
+ if (powerupjs.Touch.isTouchDevice) {
+ // get the buttons
+ var walkLeftButton = this.root.find(ID.button_walkleft);
+ var walkRightButton = this.root.find(ID.button_walkright);
+ if (walkLeftButton.down)
+ this.velocity.x = -walkingSpeed;
+ else if (walkRightButton.down)
+ this.velocity.x = walkingSpeed;
+ } else {
+ if (powerupjs.Keyboard.down(powerupjs.Keys.left))
+ this.velocity.x = -walkingSpeed;
+ else if (powerupjs.Keyboard.down(powerupjs.Keys.right))
+ this.velocity.x = walkingSpeed;
+ }
+
+ if (this.velocity.x != 0)
+ this.mirror = this.velocity.x < 0;
+
+ if (powerupjs.Touch.isTouchDevice) {
+ var jumpButton = this.root.find(ID.button_jump);
+ if (jumpButton.pressed && this.onTheGround)
+ this.jump();
+ } else {
+ if (powerupjs.Keyboard.pressed(powerupjs.Keys.space) && this.onTheGround)
+ this.jump();
+ }
+};
+
+Player.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ this.doPhysics();
+ if (!this.alive)
+ return;
+
+ if (this.onTheGround)
+ if (this.velocity.x == 0)
+ this.playAnimation("idle");
+ else
+ this.playAnimation("run");
+ else if (this.velocity.y < 0)
+ this.playAnimation("jump");
+
+ var tiles = this.root.find(ID.tiles);
+ if (this.boundingBox.top >= tiles.rows * tiles.cellHeight)
+ this.die(true);
+};
+
+Player.prototype.die = function (falling) {
+ if (!this.alive)
+ return;
+ this.alive = false;
+ this.velocity.x = 0;
+ if (falling) {
+ sounds.player_fall.play();
+ }
+ else {
+ this.velocity.y = -900;
+ sounds.player_die.play();
+ }
+ this.playAnimation("die");
+};
+
+Player.prototype.jump = function (speed) {
+ sounds.player_jump.play();
+ speed = typeof speed !== 'undefined' ? speed : 1100;
+ this.velocity.y = -speed;
+};
+
+Player.prototype.doPhysics = function () {
+ this.velocity.y += 55;
+ if (this.alive)
+ this.handleCollisions();
+};
+
+Player.prototype.handleCollisions = function () {
+ this.onTheGround = false;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+
+ var tiles = this.root.find(ID.tiles);
+
+ var x_floor = Math.floor(this.position.x / tiles.cellWidth);
+ var y_floor = Math.floor(this.position.y / tiles.cellHeight);
+
+ for (var y = y_floor - 2; y <= y_floor + 1; ++y)
+ for (var x = x_floor - 1; x <= x_floor + 1; ++x) {
+ var tileType = tiles.getTileType(x, y);
+ if (tileType === TileType.background)
+ continue;
+ var tileBounds = new powerupjs.Rectangle(x * tiles.cellWidth, y * tiles.cellHeight,
+ tiles.cellWidth, tiles.cellHeight);
+ var boundingBox = this.boundingBox;
+ boundingBox.height += 1;
+ if (!tileBounds.intersects(boundingBox))
+ continue;
+ var depth = boundingBox.calculateIntersectionDepth(tileBounds);
+ if (Math.abs(depth.x) < Math.abs(depth.y)) {
+ if (tileType === TileType.normal)
+ this.position.x += depth.x;
+ continue;
+ }
+ if (this._previousYPosition <= tileBounds.top && tileType !== TileType.background) {
+ this.onTheGround = true;
+ this.velocity.y = 0;
+ var currentTile = tiles.at(x, y);
+ if (currentTile !== null) {
+ this.walkingOnIce = this.walkingOnIce || currentTile.ice;
+ this.walkingOnHot = this.walkingOnHot || currentTile.hot;
+ }
+ }
+ if (tileType === TileType.normal || this.onTheGround)
+ this.position.y += depth.y + 1;
+ }
+ this._previousYPosition = this.position.y;
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/Tile.js b/28_PlayerInteraction/TickTick4/gameobjects/Tile.js
new file mode 100644
index 0000000..19a1c2e
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/Tile.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var TileType = {
+ background: 0,
+ normal: 1,
+ platform: 2
+};
+
+function Tile(sprite, tileTp, layer, id) {
+ sprite = typeof sprite !== 'undefined' ? sprite : sprites.wall;
+ powerupjs.SpriteGameObject.call(this, sprite, layer, id);
+
+ this.hot = false;
+ this.ice = false;
+ this.type = typeof tileTp !== 'undefined' ? tileTp : TileType.background;
+}
+
+Tile.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Tile.prototype.draw = function () {
+ if (this.type == TileType.background)
+ return;
+ powerupjs.SpriteGameObject.prototype.draw.call(this);
+};
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/TileField.js b/28_PlayerInteraction/TickTick4/gameobjects/TileField.js
new file mode 100644
index 0000000..83b0fe1
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/TileField.js
@@ -0,0 +1,15 @@
+"use strict";
+
+function TileField(rows, columns, layer, id) {
+ powerupjs.GameObjectGrid.call(this, rows, columns, layer, id);
+}
+
+TileField.prototype = Object.create(powerupjs.GameObjectGrid.prototype);
+
+TileField.prototype.getTileType = function (x, y) {
+ if (x < 0 || x >= this.columns)
+ return TileType.normal;
+ if (y < 0 || y >= this.rows)
+ return TileType.background;
+ return this.at(x, y).type;
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/WaterDrop.js b/28_PlayerInteraction/TickTick4/gameobjects/WaterDrop.js
new file mode 100644
index 0000000..06adbe8
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/WaterDrop.js
@@ -0,0 +1,22 @@
+"use strict";
+
+function WaterDrop(layer, id) {
+ powerupjs.SpriteGameObject.call(this, sprites.water, layer, id);
+ this._bounce = 0;
+}
+
+WaterDrop.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+WaterDrop.prototype.update = function (delta) {
+ this.position.y -= this._bounce;
+ var t = powerupjs.Game.totalTime * 3 + this.position.x;
+ this._bounce = Math.sin(t) * 5;
+ this.position.y += this._bounce;
+ powerupjs.SpriteGameObject.prototype.update.call(this, delta);
+
+ var player = this.root.find(ID.player);
+ if (this.collidesWith(player)) {
+ this.visible = false;
+ sounds.water_collected.play();
+ }
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/enemies/PatrollingEnemy.js b/28_PlayerInteraction/TickTick4/gameobjects/enemies/PatrollingEnemy.js
new file mode 100644
index 0000000..706571d
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/enemies/PatrollingEnemy.js
@@ -0,0 +1,49 @@
+"use strict";
+
+function PatrollingEnemy(layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this._waitTime = 0;
+ this.velocity.x = 120;
+
+ this.loadAnimation(sprites.flame, "default", true);
+ this.playAnimation("default");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+}
+PatrollingEnemy.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+PatrollingEnemy.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this._waitTime > 0) {
+ this._waitTime -= delta;
+ if (this._waitTime <= 0)
+ this.turnAround();
+ }
+ else {
+ var tiles = this.root.find(ID.tiles);
+ var posX = this.boundingBox.left;
+ if (!this.mirror)
+ posX = this.boundingBox.right;
+ var tileX = Math.floor(posX / tiles.cellWidth);
+ var tileY = Math.floor(this.position.y / tiles.cellHeight);
+ if (tiles.getTileType(tileX, tileY - 1) === TileType.normal ||
+ tiles.getTileType(tileX, tileY) === TileType.background) {
+ this._waitTime = 0.5;
+ this.velocity.x = 0;
+ }
+ }
+ this.checkPlayerCollision();
+};
+
+PatrollingEnemy.prototype.checkPlayerCollision = function () {
+ var player = this.root.find(ID.player);
+ if (this.collidesWith(player))
+ player.die(false);
+};
+
+PatrollingEnemy.prototype.turnAround = function () {
+ this.mirror = !this.mirror;
+ this.velocity.x = 120;
+ if (this.mirror)
+ this.velocity.x = -this.velocity.x;
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/enemies/PlayerFollowingEnemy.js b/28_PlayerInteraction/TickTick4/gameobjects/enemies/PlayerFollowingEnemy.js
new file mode 100644
index 0000000..f2eb88d
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/enemies/PlayerFollowingEnemy.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function PlayerFollowingEnemy(layer, id) {
+ PatrollingEnemy.call(this, layer, id);
+}
+
+PlayerFollowingEnemy.prototype = Object.create(PatrollingEnemy.prototype);
+
+PlayerFollowingEnemy.prototype.update = function (delta) {
+ PatrollingEnemy.prototype.update.call(this, delta);
+
+ var player = this.root.find(ID.player);
+ var direction = player.position.x - this.position.x;
+ if (Math.sign(direction) !== Math.sign(this.velocity.x) && player.velocity.x !== 0
+ && this.velocity.x !== 0)
+ this.turnAround();
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/enemies/Rocket.js b/28_PlayerInteraction/TickTick4/gameobjects/enemies/Rocket.js
new file mode 100644
index 0000000..db0ef7d
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/enemies/Rocket.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function Rocket(moveToLeft, startPosition, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.spawnTime = 0;
+ this.startPosition = startPosition;
+ this.mirror = moveToLeft;
+
+ this.loadAnimation(sprites.rocket, "default", true, 0.5);
+ this.playAnimation("default");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+
+ this.reset();
+}
+
+Rocket.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Rocket.prototype.reset = function () {
+ this.visible = false;
+ this.position = this.startPosition.copy();
+ this.velocity = powerupjs.Vector2.zero;
+ this.spawnTime = Math.random() * 5;
+};
+
+Rocket.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.spawnTime > 0) {
+ this.spawnTime -= delta;
+ return;
+ }
+ this.visible = true;
+ this.velocity.x = 600;
+ if (this.mirror)
+ this.velocity.x *= -1;
+ // check if we are far enough outside the screen
+ var screenBox = new powerupjs.Rectangle(-200, -200, powerupjs.Game.size.x + 400,
+ powerupjs.Game.size.y + 400);
+ if (!screenBox.intersects(this.boundingBox))
+ this.reset();
+ this.checkPlayerCollision();
+};
+
+Rocket.prototype.checkPlayerCollision = function () {
+ var player = this.root.find(ID.player);
+ if (this.collidesWith(player))
+ player.die(false);
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/enemies/Sparky.js b/28_PlayerInteraction/TickTick4/gameobjects/enemies/Sparky.js
new file mode 100644
index 0000000..8460a7a
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/enemies/Sparky.js
@@ -0,0 +1,55 @@
+"use strict";
+
+function Sparky(initialY, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.idleTime = 0;
+ this.yoffset = 0;
+ this.initialY = initialY;
+
+ this.loadAnimation(sprites.sparky_electrocute, "electrocute", false);
+ this.loadAnimation(sprites.sparky_idle, "idle", true);
+ this.playAnimation("idle");
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+ this.reset();
+}
+
+Sparky.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Sparky.prototype.reset = function () {
+ this.idleTime = Math.random() * 5;
+ this.position.y = this.initialY;
+ this.yoffset = 120;
+ this.velocity = powerupjs.Vector2.zero;
+};
+
+Sparky.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.idleTime <= 0) {
+ this.playAnimation("electrocute");
+ if (this.velocity.y != 0) {
+ // falling down or going up
+ this.yoffset -= this.velocity.y * delta;
+ if (this.yoffset <= 0)
+ this.velocity.y = 0;
+ else if (this.yoffset >= 120)
+ this.reset();
+ }
+ else if (this.animationEnded())
+ this.velocity.y = -60;
+ }
+ else {
+ this.playAnimation("idle");
+ this.idleTime -= delta;
+ if (this.idleTime <= 0)
+ this.velocity.y = 300;
+ }
+ this.checkPlayerCollision();
+};
+
+Sparky.prototype.checkPlayerCollision = function () {
+ var player = this.root.find(ID.player);
+ if (this.idleTime <= 0 && this.collidesWith(player))
+ player.die(false);
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/enemies/Turtle.js b/28_PlayerInteraction/TickTick4/gameobjects/enemies/Turtle.js
new file mode 100644
index 0000000..576f43d
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/enemies/Turtle.js
@@ -0,0 +1,49 @@
+"use strict";
+
+function Turtle(layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.waitTime = 0;
+ this.sneezing = false;
+
+ this.loadAnimation(sprites.turtle_sneeze, "sneeze", false);
+ this.loadAnimation(sprites.turtle_idle, "idle", true);
+ this.playAnimation("idle");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+
+ this.reset();
+}
+
+Turtle.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Turtle.prototype.reset = function () {
+ this.waitTime = 5;
+ this.sneezing = false;
+};
+
+Turtle.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.sneezing)
+ this.playAnimation("sneeze");
+ else
+ this.playAnimation("idle");
+
+ if (this.waitTime > 0)
+ this.waitTime -= delta;
+ else {
+ this.sneezing = !this.sneezing;
+ this.waitTime = 5;
+ }
+ this.checkPlayerCollision();
+};
+
+Turtle.prototype.checkPlayerCollision = function () {
+ var player = this.root.find(ID.player);
+ if (!this.collidesWith(player))
+ return;
+
+ if (this.sneezing)
+ player.die(false);
+ else if (player.velocity.y > 0 && player.alive)
+ player.jump(1500);
+};
diff --git a/28_PlayerInteraction/TickTick4/gameobjects/enemies/UnpredictableEnemy.js b/28_PlayerInteraction/TickTick4/gameobjects/enemies/UnpredictableEnemy.js
new file mode 100644
index 0000000..cf009ed
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gameobjects/enemies/UnpredictableEnemy.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function UnpredictableEnemy(layer, id) {
+ PatrollingEnemy.call(this, layer, id);
+}
+
+UnpredictableEnemy.prototype = Object.create(PatrollingEnemy.prototype);
+
+UnpredictableEnemy.prototype.update = function (delta) {
+ PatrollingEnemy.prototype.update.call(this, delta);
+ if (this._waitTime <= 0 && Math.random() < 0.01) {
+ this.turnAround();
+ this.velocity.x = Math.sign(this.velocity.x) *
+ Math.random() * 300;
+ }
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/gui/LevelButton.js b/28_PlayerInteraction/TickTick4/gui/LevelButton.js
new file mode 100644
index 0000000..45e419a
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/gui/LevelButton.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new powerupjs.SpriteGameObject(sprites.level_solved, ID.layer_overlays_1);
+ this._levelUnsolved = new powerupjs.SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+ this.add(this._levelUnsolved);
+
+ this._levelLocked = new powerupjs.SpriteGameObject(sprites.level_locked, ID.layer_overlays_1);
+ this.add(this._levelLocked);
+
+ var textLabel = new powerupjs.Label("Arial", "20px", ID.layer_overlays_2);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new powerupjs.Vector2(this._levelSolved.width - textLabel.width - 10, 10);
+ textLabel.color = powerupjs.Color.white;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (window.LEVELS[this.levelIndex].locked)
+ return;
+ if (powerupjs.Touch.isTouchDevice)
+ this.pressed = this.visible && powerupjs.Touch.containsTouch(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && powerupjs.Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(powerupjs.Mouse.position);
+};
+
+LevelButton.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+ var currLevel = window.LEVELS[this.levelIndex];
+ this._levelLocked.visible = currLevel.locked;
+ this._levelSolved.visible = currLevel.solved;
+ this._levelUnsolved.visible = !currLevel.solved;
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/level/Level.js b/28_PlayerInteraction/TickTick4/level/Level.js
new file mode 100644
index 0000000..84c1b6b
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/level/Level.js
@@ -0,0 +1,191 @@
+"use strict";
+
+function Level(levelIndex, id) {
+ powerupjs.GameObjectList.call(this, id);
+
+ this._levelIndex = levelIndex;
+ this._waterdrops = new powerupjs.GameObjectList(ID.layer_objects);
+ this._enemies = new powerupjs.GameObjectList(ID.layer_objects);
+ this._quitButton = new powerupjs.Button(sprites.button_quit, ID.layer_overlays);
+
+ this._quitButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - this._quitButton.width - 10, 10);
+ this.add(this._quitButton);
+
+ this.add(this._waterdrops);
+ this.add(this._enemies);
+
+ // backgrounds
+ var backgrounds = new powerupjs.GameObjectList(ID.layer_background);
+ var background_main = new powerupjs.SpriteGameObject(sprites.background_sky, ID.layer_background_1);
+ background_main.position = new powerupjs.Vector2(0, powerupjs.Game.size.y - background_main.height);
+ backgrounds.add(background_main);
+ this.add(backgrounds);
+
+ this.loadTiles();
+}
+
+Level.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Level.prototype.loadTiles = function () {
+ var levelData = window.LEVELS[this._levelIndex];
+
+ var hintField = new powerupjs.GameObjectList(ID.layer_overlays);
+ this.add(hintField);
+ var hintFrame = new powerupjs.SpriteGameObject(sprites.frame_hint, ID.layer_overlays_1);
+ hintField.position = new powerupjs.Vector2(hintFrame.screenCenterX, 10);
+ hintField.add(hintFrame);
+ var hintText = new powerupjs.Label("Arial", "14pt", ID.layer_overlays_2);
+ hintText.text = levelData.hint;
+ hintText.position = new powerupjs.Vector2(120, 25);
+ hintText.color = powerupjs.Color.black;
+ hintField.add(hintText);
+
+ if (powerupjs.Touch.isTouchDevice) {
+ var walkLeftButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_walkleft);
+ walkLeftButton.position = new powerupjs.Vector2(10, 500);
+ this.add(walkLeftButton);
+ var walkRightButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_walkright);
+ walkRightButton.position = new powerupjs.Vector2(walkRightButton.width + 20, 500);
+ walkRightButton.sheetIndex = 1;
+ this.add(walkRightButton);
+ var jumpButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_jump);
+ jumpButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - jumpButton.width - 10, 500);
+ jumpButton.sheetIndex = 2;
+ this.add(jumpButton);
+ }
+
+ var tiles = new TileField(levelData.tiles.length, levelData.tiles[0].length, 1, ID.tiles);
+ this.add(tiles);
+ tiles.cellWidth = 72;
+ tiles.cellHeight = 55;
+ for (var y = 0, ly = tiles.rows; y < ly; ++y)
+ for (var x = 0, lx = tiles.columns; x < lx; ++x) {
+ var t = this.loadTile(levelData.tiles[y][x], x, y);
+ tiles.add(t, x, y);
+ }
+};
+
+Level.prototype.loadTile = function (tileType, x, y) {
+ switch (tileType) {
+ case '.':
+ return new Tile();
+ case '-':
+ return this.loadBasicTile(sprites.platform, TileType.platform);
+ case '+':
+ return this.loadBasicTile(sprites.platform_hot, TileType.platform, true, false);
+ case '@':
+ return this.loadBasicTile(sprites.platform_ice, TileType.platform, false, true);
+ case 'X':
+ return this.loadEndTile(x, y);
+ case 'W':
+ return this.loadWaterTile(x, y);
+ case '1':
+ return this.loadStartTile(x, y);
+ case '#':
+ return this.loadBasicTile(sprites.wall, TileType.normal);
+ case '^':
+ return this.loadBasicTile(sprites.wall_hot, TileType.normal, true, false);
+ case '*':
+ return this.loadBasicTile(sprites.wall_ice, TileType.normal, false, true);
+ case 'R':
+ return this.loadRocketTile(x, y, true);
+ case 'r':
+ return this.loadRocketTile(x, y, false);
+ case 'S':
+ return this.loadSparkyTile(x, y);
+ case 'T':
+ return this.loadTurtleTile(x, y);
+ case 'A':
+ case 'B':
+ case 'C':
+ return this.loadFlameTile(x, y, tileType);
+
+ default:
+ return new Tile();
+ }
+};
+
+Level.prototype.loadBasicTile = function (id, tileType, hot, ice) {
+ var t = new Tile(id, tileType);
+ t.hot = hot;
+ t.ice = ice;
+ return t;
+};
+
+Level.prototype.loadStartTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var startPosition = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ this.add(new Player(startPosition, ID.layer_objects, ID.player));
+ return new Tile();
+};
+
+Level.prototype.loadEndTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var exit = new powerupjs.SpriteGameObject(sprites.goal, ID.layer_objects, ID.exit);
+ exit.position = new powerupjs.Vector2(x * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ exit.origin = new powerupjs.Vector2(0, exit.height);
+ this.add(exit);
+ return new Tile();
+};
+
+Level.prototype.loadWaterTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var w = new WaterDrop(ID.layer_objects);
+ w.origin = w.center.copy();
+ w.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 0.5) * tiles.cellHeight - 10);
+ this._waterdrops.add(w);
+ return new Tile();
+};
+
+Level.prototype.loadRocketTile = function (x, y, moveToLeft) {
+ var tiles = this.find(ID.tiles);
+ var startPosition = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ var enemy = new Rocket(moveToLeft, startPosition, ID.layer_objects);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadTurtleTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var enemy = new Turtle(ID.layer_objects);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight + 25);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadSparkyTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var enemy = new Sparky((y + 1) * tiles.cellHeight - 100, ID.layer_objects);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight - 100);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadFlameTile = function (x, y, enemyType) {
+ var enemy = null;
+ switch (enemyType) {
+ case 'A':
+ enemy = new UnpredictableEnemy(ID.layer_objects);
+ break;
+ case 'B':
+ enemy = new PlayerFollowingEnemy(ID.layer_objects);
+ break;
+ case 'C':
+ default:
+ enemy = new PatrollingEnemy(ID.layer_objects);
+ break;
+ }
+ var tiles = this.find(ID.tiles);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ if (!this._quitButton.pressed)
+ return;
+ this.reset();
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/states/HelpState.js b/28_PlayerInteraction/TickTick4/states/HelpState.js
new file mode 100644
index 0000000..c71e040
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/states/HelpState.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function HelpState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the background
+ var background = new powerupjs.SpriteGameObject(sprites.background_help, ID.layer_background);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new powerupjs.Vector2(this.backButton.screenCenterX, 750);
+ this.add(this.backButton);
+}
+HelpState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/states/LevelMenuState.js b/28_PlayerInteraction/TickTick4/states/LevelMenuState.js
new file mode 100644
index 0000000..c00b239
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/states/LevelMenuState.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ this.background = new powerupjs.SpriteGameObject(sprites.levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new powerupjs.Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0, j = window.LEVELS.length; i < j; ++i) {
+ var row = Math.floor(i / 4);
+ var column = i % 4;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new powerupjs.Vector2(column * (level.width + 20) + 390, row * (level.height + 20) + 180);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i++) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ var playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ playingState.goToLevel(selectedLevel);
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+ }
+ else if (this.back.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/states/PlayingState.js b/28_PlayerInteraction/TickTick4/states/PlayingState.js
new file mode 100644
index 0000000..52093c9
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/states/PlayingState.js
@@ -0,0 +1,78 @@
+"use strict";
+
+function PlayingState() {
+ powerupjs.IGameLoopObject.call(this);
+
+ this.currentLevelIndex = -1;
+ this.levels = [];
+
+ this.loadLevelsStatus();
+ this.loadLevels();
+
+ //--- CODE ADDED FOR TESTING ---
+ // In the lines below, I set all levels to unlocked so you can test the additions to the game.
+ // In the final version of the game (Ch 29), these lines are removed again
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel += 1) {
+ window.LEVELS[currLevel].locked = false;
+ }
+ //--- END CODE ADDED FOR TESTING ---
+}
+
+PlayingState.prototype = Object.create(powerupjs.IGameLoopObject.prototype);
+
+Object.defineProperty(PlayingState.prototype, "currentLevel", {
+ get: function () {
+ return this.levels[this.currentLevelIndex];
+ }
+});
+
+PlayingState.prototype.handleInput = function (delta) {
+ this.currentLevel.handleInput(delta);
+};
+
+PlayingState.prototype.update = function (delta) {
+ this.currentLevel.update(delta);
+};
+
+PlayingState.prototype.draw = function () {
+ this.currentLevel.draw();
+};
+
+PlayingState.prototype.reset = function () {
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.goToLevel = function (levelIndex) {
+ if (levelIndex < 0 || levelIndex >= this.levels.length)
+ return;
+ this.currentLevelIndex = levelIndex;
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.nextLevel = function () {
+ if (this.currentLevelIndex >= window.LEVELS.length - 1)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else {
+ this.goToLevel(this.currentLevelIndex + 1);
+ window.LEVELS[this.currentLevelIndex].locked = false;
+ }
+ this.writeLevelsStatus();
+};
+
+PlayingState.prototype.loadLevels = function () {
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel += 1) {
+ this.levels.push(new Level(currLevel));
+ }
+};
+
+PlayingState.prototype.loadLevelsStatus = function () {
+ if (localStorage && localStorage.tickTickLevels) {
+ window.LEVELS = JSON.parse(localStorage.tickTickLevels);
+ }
+};
+
+PlayingState.prototype.writeLevelsStatus = function () {
+ if (!localStorage)
+ return;
+ localStorage.tickTickLevels = JSON.stringify(window.LEVELS);
+};
\ No newline at end of file
diff --git a/28_PlayerInteraction/TickTick4/states/TitleMenuState.js b/28_PlayerInteraction/TickTick4/states/TitleMenuState.js
new file mode 100644
index 0000000..0628cb6
--- /dev/null
+++ b/28_PlayerInteraction/TickTick4/states/TitleMenuState.js
@@ -0,0 +1,29 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the title screen
+ var titleScreen = new powerupjs.SpriteGameObject(sprites.background_title, ID.layer_background);
+ this.add(titleScreen);
+
+ // add a play button
+ this.playButton = new powerupjs.Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new powerupjs.Vector2(this.playButton.screenCenterX, 540);
+ this.add(this.playButton);
+
+ // add a help button
+ this.helpButton = new powerupjs.Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new powerupjs.Vector2(this.helpButton.screenCenterX, 600);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_help);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/LAB.min.js b/29_FinishingGame/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/29_FinishingGame/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/TickTick.html b/29_FinishingGame/TickTickFinal/TickTick.html
new file mode 100644
index 0000000..3bf4d10
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/TickTick.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+ TickTick
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/TickTick.js b/29_FinishingGame/TickTickFinal/TickTick.js
new file mode 100644
index 0000000..607739d
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/TickTick.js
@@ -0,0 +1,115 @@
+"use strict";
+
+var ID = {};
+var sprites = {};
+var sounds = {};
+
+powerupjs.Game.loadAssets = function () {
+ var loadSprite = function (sprite, collisionMask) {
+ return new powerupjs.SpriteSheet("../../assets/TickTick/sprites/" + sprite/*, collisionMask*/);
+ };
+
+ var loadSound = function (sound, looping) {
+ return new powerupjs.Sound("../../assets/TickTick/sounds/" + sound, looping);
+ };
+
+ sprites.background_title = loadSprite("backgrounds/spr_title.jpg");
+ sprites.background_help = loadSprite("backgrounds/spr_help.jpg");
+ sprites.background_sky = loadSprite("backgrounds/spr_sky.jpg");
+ sprites.cloud_1 = loadSprite("backgrounds/spr_cloud_1.png");
+ sprites.cloud_2 = loadSprite("backgrounds/spr_cloud_2.png");
+ sprites.cloud_3 = loadSprite("backgrounds/spr_cloud_3.png");
+ sprites.cloud_4 = loadSprite("backgrounds/spr_cloud_4.png");
+ sprites.cloud_5 = loadSprite("backgrounds/spr_cloud_5.png");
+ sprites.mountain_1 = loadSprite("backgrounds/spr_mountain_1.png");
+ sprites.mountain_2 = loadSprite("backgrounds/spr_mountain_2.png");
+ sprites.levelselect = loadSprite("backgrounds/spr_levelselect.jpg");
+
+ if (powerupjs.Touch.isTouchDevice) {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_tap.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_tap.png");
+ } else {
+ sprites.overlay_gameover = loadSprite("overlays/spr_gameover_click.png");
+ sprites.overlay_welldone = loadSprite("overlays/spr_welldone_click.png");
+ }
+ sprites.frame_hint = loadSprite("overlays/spr_frame_hint.png");
+ sprites.timer = loadSprite("overlays/spr_timer.png");
+
+ sprites.button_play = loadSprite("gui/spr_button_play.png");
+ sprites.button_help = loadSprite("gui/spr_button_help.png");
+ sprites.button_back = loadSprite("gui/spr_button_back.png");
+ sprites.button_quit = loadSprite("gui/spr_button_quit.png");
+ sprites.level_solved = loadSprite("gui/spr_level_solved.png");
+ sprites.level_unsolved = loadSprite("gui/spr_level_unsolved.png");
+ sprites.level_locked = loadSprite("gui/spr_level_locked.png");
+ sprites.buttons_player = loadSprite("gui/spr_buttons_player@3.png");
+
+ sprites.wall = loadSprite("tiles/spr_wall.png");
+ sprites.wall_hot = loadSprite("tiles/spr_wall_hot.png");
+ sprites.wall_ice = loadSprite("tiles/spr_wall_ice.png");
+ sprites.platform = loadSprite("tiles/spr_platform.png");
+ sprites.platform_hot = loadSprite("tiles/spr_platform_hot.png");
+ sprites.platform_ice = loadSprite("tiles/spr_platform_ice.png");
+
+ sprites.goal = loadSprite("spr_goal.png", true);
+ sprites.water = loadSprite("spr_water.png");
+
+ // player animations
+ sprites.player_idle = loadSprite("player/spr_idle.png", true);
+ sprites.player_run = loadSprite("player/spr_run@13.png", true);
+ sprites.player_jump = loadSprite("player/spr_jump@14.png", true);
+ sprites.player_celebrate = loadSprite("player/spr_celebrate@14.png");
+ sprites.player_die = loadSprite("player/spr_die@5.png");
+ sprites.player_explode = loadSprite("player/spr_explode@5x5.png");
+
+ // enemy animations
+ sprites.rocket = loadSprite("rocket/spr_rocket@3.png", true);
+ sprites.flame = loadSprite("flame/spr_flame@9.png", true);
+ sprites.turtle_sneeze = loadSprite("turtle/spr_sneeze@9.png", true);
+ sprites.turtle_idle = loadSprite("turtle/spr_idle.png", true);
+ sprites.sparky_electrocute = loadSprite("sparky/spr_electrocute@6x5.png", true);
+ sprites.sparky_idle = loadSprite("sparky/spr_idle.png", true);
+
+ sounds.music = loadSound("snd_music", true);
+ sounds.player_die = loadSound("snd_player_die");
+ sounds.player_explode = loadSound("snd_player_explode");
+ sounds.player_fall = loadSound("snd_player_fall");
+ sounds.player_jump = loadSound("snd_player_jump");
+ sounds.player_won = loadSound("snd_player_won");
+ sounds.water_collected = loadSound("snd_water_collected");
+};
+
+powerupjs.Game.initialize = function () {
+ // play the music
+ sounds.music.volume = 0.3;
+ sounds.music.play();
+
+ // define the layers
+ ID.layer_background = 1;
+ ID.layer_background_1 = 2;
+ ID.layer_background_2 = 3;
+ ID.layer_background_3 = 4;
+ ID.layer_tiles = 10;
+ ID.layer_objects = 20;
+ ID.layer_overlays = 30;
+ ID.layer_overlays_1 = 31;
+ ID.layer_overlays_2 = 32;
+
+ // define object IDs
+ ID.player = 1;
+ ID.timer = 2;
+ ID.tiles = 3;
+ ID.exit = 4;
+ ID.hint_timer = 5;
+ ID.button_walkleft = 6;
+ ID.button_walkright = 7;
+ ID.button_jump = 8;
+
+ ID.game_state_title = powerupjs.GameStateManager.add(new TitleMenuState());
+ ID.game_state_help = powerupjs.GameStateManager.add(new HelpState());
+ ID.game_state_playing = powerupjs.GameStateManager.add(new PlayingState());
+ ID.game_state_levelselect = powerupjs.GameStateManager.add(new LevelMenuState());
+ ID.game_state_gameover = powerupjs.GameStateManager.add(new GameOverState());
+ ID.game_state_levelfinished = powerupjs.GameStateManager.add(new LevelFinishedState());
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/game-layout.css b/29_FinishingGame/TickTickFinal/game-layout.css
new file mode 100644
index 0000000..e3fa340
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/game-layout.css
@@ -0,0 +1,13 @@
+html, body {
+ margin: 0;
+}
+
+#gameArea {
+ position: absolute;
+}
+
+#mycanvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/Clouds.js b/29_FinishingGame/TickTickFinal/gameobjects/Clouds.js
new file mode 100644
index 0000000..2a6bcf4
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/Clouds.js
@@ -0,0 +1,34 @@
+"use strict";
+
+function Clouds(layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+ for (var i = 0; i < 3; i++) {
+ var cloud = new powerupjs.SpriteGameObject(sprites["cloud_" + Math.ceil(Math.random()*5)]);
+ cloud.position = new powerupjs.Vector2(Math.random() * powerupjs.Game.size.x - cloud.width / 2,
+ Math.random() * powerupjs.Game.size.y - cloud.height / 2);
+ cloud.velocity = new powerupjs.Vector2((Math.random() * 2 - 1) * 20, 0);
+ this.add(cloud);
+ }
+}
+
+Clouds.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Clouds.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+
+ for (var i = 0, l = this.length; i < l; ++i) {
+ var c = this.at(i);
+ if ((c.velocity.x < 0 && c.position.x + c.width < 0) || (c.velocity.x > 0 && c.position.x > powerupjs.Game.size.x)) {
+ this.remove(c);
+ var cloud = new powerupjs.SpriteGameObject(sprites["cloud_" + Math.ceil(Math.random()*5)]);
+ cloud.velocity = new powerupjs.Vector2(((Math.random() * 2) - 1) * 20, 0);
+ var cloudHeight = Math.random() * powerupjs.Game.size.y - cloud.height / 2;
+ if (cloud.velocity.x < 0)
+ cloud.position = new powerupjs.Vector2(powerupjs.Game.size.x, cloudHeight);
+ else
+ cloud.position = new powerupjs.Vector2(-cloud.width, cloudHeight);
+ this.add(cloud);
+ return;
+ }
+ }
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/Player.js b/29_FinishingGame/TickTickFinal/gameobjects/Player.js
new file mode 100644
index 0000000..3b44619
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/Player.js
@@ -0,0 +1,195 @@
+"use strict";
+
+function Player(start, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+ this._startPosition = start;
+ this._previousYPosition = 0;
+
+ this.onTheGround = false;
+ this.alive = true;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+ this.finished = false;
+ this.exploded = false;
+
+ this.loadAnimation(sprites.player_idle, "idle", true);
+ this.loadAnimation(sprites.player_run, "run", true, 0.05);
+ this.loadAnimation(sprites.player_jump, "jump", false, 0.05);
+ this.loadAnimation(sprites.player_celebrate, "celebrate", false, 0.05);
+ this.loadAnimation(sprites.player_die, "die", false);
+ this.loadAnimation(sprites.player_explode, "explode", false, 0.04);
+
+ this.reset();
+}
+
+Player.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Player.prototype.reset = function () {
+ this.playAnimation("idle");
+
+ this._previousYPosition = this.boundingBox.bottom;
+ this.position = this._startPosition.copy();
+ this.velocity = powerupjs.Vector2.zero;
+
+ this.onTheGround = true;
+ this.alive = true;
+ this.exploded = false;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+ this.finished = false;
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+};
+
+Player.prototype.handleInput = function (delta) {
+ if (!this.alive || this.finished)
+ return;
+ var walkingSpeed = 400;
+ if (this.walkingOnIce) {
+ walkingSpeed *= 1.5;
+ this.velocity.x = Math.sign(this.velocity.x) * walkingSpeed;
+ } else if (this.onTheGround)
+ this.velocity.x = 0;
+
+ if (powerupjs.Touch.isTouchDevice) {
+ // get the buttons
+ var walkLeftButton = this.root.find(ID.button_walkleft);
+ var walkRightButton = this.root.find(ID.button_walkright);
+ if (walkLeftButton.down)
+ this.velocity.x = -walkingSpeed;
+ else if (walkRightButton.down)
+ this.velocity.x = walkingSpeed;
+ } else {
+ if (powerupjs.Keyboard.down(powerupjs.Keys.left))
+ this.velocity.x = -walkingSpeed;
+ else if (powerupjs.Keyboard.down(powerupjs.Keys.right))
+ this.velocity.x = walkingSpeed;
+ }
+
+ if (this.velocity.x != 0)
+ this.mirror = this.velocity.x < 0;
+
+ if (powerupjs.Touch.isTouchDevice) {
+ var jumpButton = this.root.find(ID.button_jump);
+ if (jumpButton.pressed && this.onTheGround)
+ this.jump();
+ } else {
+ if (powerupjs.Keyboard.pressed(powerupjs.Keys.space) && this.onTheGround)
+ this.jump();
+ }
+};
+
+Player.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ this.doPhysics();
+ if (!this.alive || this.finished)
+ return;
+
+ if (this.onTheGround)
+ if (this.velocity.x == 0)
+ this.playAnimation("idle");
+ else
+ this.playAnimation("run");
+ else if (this.velocity.y < 0)
+ this.playAnimation("jump");
+
+ var timer = this.root.find(ID.timer);
+ if (this.walkingOnHot)
+ timer.multiplier = 2;
+ else if (this.walkingOnIce)
+ timer.multiplier = 0.5;
+ else
+ timer.multiplier = 1;
+
+ var tiles = this.root.find(ID.tiles);
+ if (this.boundingBox.top >= tiles.rows * tiles.cellHeight)
+ this.die(true);
+};
+
+Player.prototype.die = function (falling) {
+ if (!this.alive || this.finished)
+ return;
+ this.alive = false;
+ this.velocity.x = 0;
+ if (falling) {
+ sounds.player_fall.play();
+ }
+ else {
+ this.velocity.y = -900;
+ sounds.player_die.play();
+ }
+ this.playAnimation("die");
+};
+
+Player.prototype.explode = function () {
+ if (!this.alive || this.finished)
+ return;
+ this.alive = false;
+ this.exploded = true;
+ this.velocity = powerupjs.Vector2.zero;
+ this.playAnimation("explode");
+ sounds.player_explode.play();
+};
+
+Player.prototype.levelFinished = function () {
+ this.finished = true;
+ this.velocity.x = 0;
+ this.playAnimation("celebrate");
+ sounds.player_won.play();
+};
+
+Player.prototype.jump = function (speed) {
+ sounds.player_jump.play();
+ speed = typeof speed !== 'undefined' ? speed : 1100;
+ this.velocity.y = -speed;
+};
+
+Player.prototype.doPhysics = function () {
+ if (!this.exploded)
+ this.velocity.y += 55;
+ if (this.alive)
+ this.handleCollisions();
+};
+
+Player.prototype.handleCollisions = function () {
+ this.onTheGround = false;
+ this.walkingOnIce = false;
+ this.walkingOnHot = false;
+
+ var tiles = this.root.find(ID.tiles);
+
+ var x_floor = Math.floor(this.position.x / tiles.cellWidth);
+ var y_floor = Math.floor(this.position.y / tiles.cellHeight);
+
+ for (var y = y_floor - 2; y <= y_floor + 1; ++y)
+ for (var x = x_floor - 1; x <= x_floor + 1; ++x) {
+ var tileType = tiles.getTileType(x, y);
+ if (tileType === TileType.background)
+ continue;
+ var tileBounds = new powerupjs.Rectangle(x * tiles.cellWidth, y * tiles.cellHeight,
+ tiles.cellWidth, tiles.cellHeight);
+ var boundingBox = this.boundingBox;
+ boundingBox.height += 1;
+ if (!tileBounds.intersects(boundingBox))
+ continue;
+ var depth = boundingBox.calculateIntersectionDepth(tileBounds);
+ if (Math.abs(depth.x) < Math.abs(depth.y)) {
+ if (tileType === TileType.normal)
+ this.position.x += depth.x;
+ continue;
+ }
+ if (this._previousYPosition <= tileBounds.top && tileType !== TileType.background) {
+ this.onTheGround = true;
+ this.velocity.y = 0;
+ var currentTile = tiles.at(x, y);
+ if (currentTile !== null) {
+ this.walkingOnIce = this.walkingOnIce || currentTile.ice;
+ this.walkingOnHot = this.walkingOnHot || currentTile.hot;
+ }
+ }
+ if (tileType === TileType.normal || this.onTheGround)
+ this.position.y += depth.y + 1;
+ }
+ this._previousYPosition = this.position.y;
+};
+
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/Tile.js b/29_FinishingGame/TickTickFinal/gameobjects/Tile.js
new file mode 100644
index 0000000..19a1c2e
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/Tile.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var TileType = {
+ background: 0,
+ normal: 1,
+ platform: 2
+};
+
+function Tile(sprite, tileTp, layer, id) {
+ sprite = typeof sprite !== 'undefined' ? sprite : sprites.wall;
+ powerupjs.SpriteGameObject.call(this, sprite, layer, id);
+
+ this.hot = false;
+ this.ice = false;
+ this.type = typeof tileTp !== 'undefined' ? tileTp : TileType.background;
+}
+
+Tile.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+Tile.prototype.draw = function () {
+ if (this.type == TileType.background)
+ return;
+ powerupjs.SpriteGameObject.prototype.draw.call(this);
+};
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/TileField.js b/29_FinishingGame/TickTickFinal/gameobjects/TileField.js
new file mode 100644
index 0000000..83b0fe1
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/TileField.js
@@ -0,0 +1,15 @@
+"use strict";
+
+function TileField(rows, columns, layer, id) {
+ powerupjs.GameObjectGrid.call(this, rows, columns, layer, id);
+}
+
+TileField.prototype = Object.create(powerupjs.GameObjectGrid.prototype);
+
+TileField.prototype.getTileType = function (x, y) {
+ if (x < 0 || x >= this.columns)
+ return TileType.normal;
+ if (y < 0 || y >= this.rows)
+ return TileType.background;
+ return this.at(x, y).type;
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/TimerGameObject.js b/29_FinishingGame/TickTickFinal/gameobjects/TimerGameObject.js
new file mode 100644
index 0000000..1a0e8bb
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/TimerGameObject.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function TimerGameObject(layer, id) {
+ powerupjs.Label.call(this, "Arial", "26pt", layer, id);
+ this.reset()
+}
+TimerGameObject.prototype = Object.create(powerupjs.Label.prototype);
+
+Object.defineProperty(TimerGameObject.prototype, "gameOver",
+ {
+ get: function () {
+ return this._timeLeft <= 0;
+ }
+ });
+
+TimerGameObject.prototype.update = function (delta) {
+ if (!this.running)
+ return;
+ this._timeLeft -= delta * this.multiplier;
+ if (this._timeLeft < 0)
+ this.running = false;
+
+ var minutes = Math.floor(this._timeLeft / 60);
+ var seconds = Math.ceil(this._timeLeft % 60);
+ if (this._timeLeft < 0)
+ minutes = seconds = 0;
+ this.text = minutes + ":" + seconds;
+ if (seconds < 10)
+ this.text = minutes + ":0" + seconds;
+ this.color = powerupjs.Color.yellow;
+ if (this._timeLeft <= 10 && seconds % 2 === 0)
+ this.color = powerupjs.Color.red;
+};
+
+TimerGameObject.prototype.reset = function () {
+ powerupjs.Label.prototype.reset.call(this);
+ this._timeLeft = 30;
+ this.running = true;
+ this.multiplier = 1;
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/VisibilityTimer.js b/29_FinishingGame/TickTickFinal/gameobjects/VisibilityTimer.js
new file mode 100644
index 0000000..22e5b32
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/VisibilityTimer.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function VisibilityTimer(target, layer, id) {
+ powerupjs.GameObject.call(this, layer, id);
+ this._target = target;
+ this._timeLeft = 0;
+ this._totalTime = 5;
+}
+
+VisibilityTimer.prototype = Object.create(powerupjs.GameObject.prototype);
+
+VisibilityTimer.prototype.update = function (delta) {
+ this._timeLeft -= delta;
+ if (this._timeLeft <= 0)
+ this._target.visible = false;
+};
+
+VisibilityTimer.prototype.startVisible = function () {
+ this._timeLeft = this._totalTime;
+ this._target.visible = true;
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/WaterDrop.js b/29_FinishingGame/TickTickFinal/gameobjects/WaterDrop.js
new file mode 100644
index 0000000..3e83635
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/WaterDrop.js
@@ -0,0 +1,23 @@
+"use strict";
+
+function WaterDrop(layer, id) {
+ powerupjs.SpriteGameObject.call(this, sprites.water, layer, id);
+ this._bounce = 0;
+}
+
+WaterDrop.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+WaterDrop.prototype.update = function (delta) {
+ this.position.y -= this._bounce;
+ var t = powerupjs.Game.totalTime * 3 + this.position.x;
+ this._bounce = Math.sin(t) * 5;
+ this.position.y += this._bounce;
+ powerupjs.SpriteGameObject.prototype.update.call(this, delta);
+
+ var player = this.root.find(ID.player);
+ if (this.collidesWith(player)) {
+ this.visible = false;
+ sounds.water_collected.play();
+
+ }
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/enemies/PatrollingEnemy.js b/29_FinishingGame/TickTickFinal/gameobjects/enemies/PatrollingEnemy.js
new file mode 100644
index 0000000..706571d
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/enemies/PatrollingEnemy.js
@@ -0,0 +1,49 @@
+"use strict";
+
+function PatrollingEnemy(layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this._waitTime = 0;
+ this.velocity.x = 120;
+
+ this.loadAnimation(sprites.flame, "default", true);
+ this.playAnimation("default");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+}
+PatrollingEnemy.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+PatrollingEnemy.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this._waitTime > 0) {
+ this._waitTime -= delta;
+ if (this._waitTime <= 0)
+ this.turnAround();
+ }
+ else {
+ var tiles = this.root.find(ID.tiles);
+ var posX = this.boundingBox.left;
+ if (!this.mirror)
+ posX = this.boundingBox.right;
+ var tileX = Math.floor(posX / tiles.cellWidth);
+ var tileY = Math.floor(this.position.y / tiles.cellHeight);
+ if (tiles.getTileType(tileX, tileY - 1) === TileType.normal ||
+ tiles.getTileType(tileX, tileY) === TileType.background) {
+ this._waitTime = 0.5;
+ this.velocity.x = 0;
+ }
+ }
+ this.checkPlayerCollision();
+};
+
+PatrollingEnemy.prototype.checkPlayerCollision = function () {
+ var player = this.root.find(ID.player);
+ if (this.collidesWith(player))
+ player.die(false);
+};
+
+PatrollingEnemy.prototype.turnAround = function () {
+ this.mirror = !this.mirror;
+ this.velocity.x = 120;
+ if (this.mirror)
+ this.velocity.x = -this.velocity.x;
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/enemies/PlayerFollowingEnemy.js b/29_FinishingGame/TickTickFinal/gameobjects/enemies/PlayerFollowingEnemy.js
new file mode 100644
index 0000000..f2eb88d
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/enemies/PlayerFollowingEnemy.js
@@ -0,0 +1,17 @@
+"use strict";
+
+function PlayerFollowingEnemy(layer, id) {
+ PatrollingEnemy.call(this, layer, id);
+}
+
+PlayerFollowingEnemy.prototype = Object.create(PatrollingEnemy.prototype);
+
+PlayerFollowingEnemy.prototype.update = function (delta) {
+ PatrollingEnemy.prototype.update.call(this, delta);
+
+ var player = this.root.find(ID.player);
+ var direction = player.position.x - this.position.x;
+ if (Math.sign(direction) !== Math.sign(this.velocity.x) && player.velocity.x !== 0
+ && this.velocity.x !== 0)
+ this.turnAround();
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/enemies/Rocket.js b/29_FinishingGame/TickTickFinal/gameobjects/enemies/Rocket.js
new file mode 100644
index 0000000..db0ef7d
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/enemies/Rocket.js
@@ -0,0 +1,48 @@
+"use strict";
+
+function Rocket(moveToLeft, startPosition, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.spawnTime = 0;
+ this.startPosition = startPosition;
+ this.mirror = moveToLeft;
+
+ this.loadAnimation(sprites.rocket, "default", true, 0.5);
+ this.playAnimation("default");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+
+ this.reset();
+}
+
+Rocket.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Rocket.prototype.reset = function () {
+ this.visible = false;
+ this.position = this.startPosition.copy();
+ this.velocity = powerupjs.Vector2.zero;
+ this.spawnTime = Math.random() * 5;
+};
+
+Rocket.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.spawnTime > 0) {
+ this.spawnTime -= delta;
+ return;
+ }
+ this.visible = true;
+ this.velocity.x = 600;
+ if (this.mirror)
+ this.velocity.x *= -1;
+ // check if we are far enough outside the screen
+ var screenBox = new powerupjs.Rectangle(-200, -200, powerupjs.Game.size.x + 400,
+ powerupjs.Game.size.y + 400);
+ if (!screenBox.intersects(this.boundingBox))
+ this.reset();
+ this.checkPlayerCollision();
+};
+
+Rocket.prototype.checkPlayerCollision = function () {
+ var player = this.root.find(ID.player);
+ if (this.collidesWith(player))
+ player.die(false);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/enemies/Sparky.js b/29_FinishingGame/TickTickFinal/gameobjects/enemies/Sparky.js
new file mode 100644
index 0000000..8460a7a
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/enemies/Sparky.js
@@ -0,0 +1,55 @@
+"use strict";
+
+function Sparky(initialY, layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.idleTime = 0;
+ this.yoffset = 0;
+ this.initialY = initialY;
+
+ this.loadAnimation(sprites.sparky_electrocute, "electrocute", false);
+ this.loadAnimation(sprites.sparky_idle, "idle", true);
+ this.playAnimation("idle");
+
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+ this.reset();
+}
+
+Sparky.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Sparky.prototype.reset = function () {
+ this.idleTime = Math.random() * 5;
+ this.position.y = this.initialY;
+ this.yoffset = 120;
+ this.velocity = powerupjs.Vector2.zero;
+};
+
+Sparky.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.idleTime <= 0) {
+ this.playAnimation("electrocute");
+ if (this.velocity.y != 0) {
+ // falling down or going up
+ this.yoffset -= this.velocity.y * delta;
+ if (this.yoffset <= 0)
+ this.velocity.y = 0;
+ else if (this.yoffset >= 120)
+ this.reset();
+ }
+ else if (this.animationEnded())
+ this.velocity.y = -60;
+ }
+ else {
+ this.playAnimation("idle");
+ this.idleTime -= delta;
+ if (this.idleTime <= 0)
+ this.velocity.y = 300;
+ }
+ this.checkPlayerCollision();
+};
+
+Sparky.prototype.checkPlayerCollision = function () {
+ var player = this.root.find(ID.player);
+ if (this.idleTime <= 0 && this.collidesWith(player))
+ player.die(false);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/enemies/Turtle.js b/29_FinishingGame/TickTickFinal/gameobjects/enemies/Turtle.js
new file mode 100644
index 0000000..576f43d
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/enemies/Turtle.js
@@ -0,0 +1,49 @@
+"use strict";
+
+function Turtle(layer, id) {
+ powerupjs.AnimatedGameObject.call(this, layer, id);
+
+ this.waitTime = 0;
+ this.sneezing = false;
+
+ this.loadAnimation(sprites.turtle_sneeze, "sneeze", false);
+ this.loadAnimation(sprites.turtle_idle, "idle", true);
+ this.playAnimation("idle");
+ this.origin = new powerupjs.Vector2(this.width / 2, this.height);
+
+ this.reset();
+}
+
+Turtle.prototype = Object.create(powerupjs.AnimatedGameObject.prototype);
+
+Turtle.prototype.reset = function () {
+ this.waitTime = 5;
+ this.sneezing = false;
+};
+
+Turtle.prototype.update = function (delta) {
+ powerupjs.AnimatedGameObject.prototype.update.call(this, delta);
+ if (this.sneezing)
+ this.playAnimation("sneeze");
+ else
+ this.playAnimation("idle");
+
+ if (this.waitTime > 0)
+ this.waitTime -= delta;
+ else {
+ this.sneezing = !this.sneezing;
+ this.waitTime = 5;
+ }
+ this.checkPlayerCollision();
+};
+
+Turtle.prototype.checkPlayerCollision = function () {
+ var player = this.root.find(ID.player);
+ if (!this.collidesWith(player))
+ return;
+
+ if (this.sneezing)
+ player.die(false);
+ else if (player.velocity.y > 0 && player.alive)
+ player.jump(1500);
+};
diff --git a/29_FinishingGame/TickTickFinal/gameobjects/enemies/UnpredictableEnemy.js b/29_FinishingGame/TickTickFinal/gameobjects/enemies/UnpredictableEnemy.js
new file mode 100644
index 0000000..cf009ed
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gameobjects/enemies/UnpredictableEnemy.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function UnpredictableEnemy(layer, id) {
+ PatrollingEnemy.call(this, layer, id);
+}
+
+UnpredictableEnemy.prototype = Object.create(PatrollingEnemy.prototype);
+
+UnpredictableEnemy.prototype.update = function (delta) {
+ PatrollingEnemy.prototype.update.call(this, delta);
+ if (this._waitTime <= 0 && Math.random() < 0.01) {
+ this.turnAround();
+ this.velocity.x = Math.sign(this.velocity.x) *
+ Math.random() * 300;
+ }
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/gui/LevelButton.js b/29_FinishingGame/TickTickFinal/gui/LevelButton.js
new file mode 100644
index 0000000..45e419a
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/gui/LevelButton.js
@@ -0,0 +1,57 @@
+"use strict";
+
+function LevelButton(levelIndex, layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+
+ this.pressed = false;
+ this.levelIndex = levelIndex;
+ this._levelSolved = new powerupjs.SpriteGameObject(sprites.level_solved, ID.layer_overlays_1);
+ this._levelUnsolved = new powerupjs.SpriteGameObject(sprites.level_unsolved, ID.layer_overlays);
+
+ this._levelSolved.sheetIndex = levelIndex;
+ this.add(this._levelSolved);
+ this.add(this._levelUnsolved);
+
+ this._levelLocked = new powerupjs.SpriteGameObject(sprites.level_locked, ID.layer_overlays_1);
+ this.add(this._levelLocked);
+
+ var textLabel = new powerupjs.Label("Arial", "20px", ID.layer_overlays_2);
+ textLabel.text = levelIndex + 1;
+ textLabel.position = new powerupjs.Vector2(this._levelSolved.width - textLabel.width - 10, 10);
+ textLabel.color = powerupjs.Color.white;
+ this.add(textLabel);
+}
+
+LevelButton.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(LevelButton.prototype, "width",
+ {
+ get: function () {
+ return this._levelLocked.width;
+ }
+ });
+
+Object.defineProperty(LevelButton.prototype, "height",
+ {
+ get: function () {
+ return this._levelLocked.height;
+ }
+ });
+
+LevelButton.prototype.handleInput = function (delta) {
+ if (window.LEVELS[this.levelIndex].locked)
+ return;
+ if (powerupjs.Touch.isTouchDevice)
+ this.pressed = this.visible && powerupjs.Touch.containsTouch(this._levelLocked.boundingBox);
+ else
+ this.pressed = this.visible && powerupjs.Mouse.left.pressed &&
+ this._levelLocked.boundingBox.contains(powerupjs.Mouse.position);
+};
+
+LevelButton.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+ var currLevel = window.LEVELS[this.levelIndex];
+ this._levelLocked.visible = currLevel.locked;
+ this._levelSolved.visible = currLevel.solved;
+ this._levelUnsolved.visible = !currLevel.solved;
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/level/Level.js b/29_FinishingGame/TickTickFinal/level/Level.js
new file mode 100644
index 0000000..5b4fc57
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/level/Level.js
@@ -0,0 +1,264 @@
+"use strict";
+
+function Level(levelIndex, id) {
+ powerupjs.GameObjectList.call(this, id);
+
+ this._levelIndex = levelIndex;
+ this._waterdrops = new powerupjs.GameObjectList(ID.layer_objects);
+ this._enemies = new powerupjs.GameObjectList(ID.layer_objects);
+ this._quitButton = new powerupjs.Button(sprites.button_quit, ID.layer_overlays);
+
+ this._quitButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - this._quitButton.width - 10, 10);
+ this.add(this._quitButton);
+
+ this.add(this._waterdrops);
+ this.add(this._enemies);
+
+ // backgrounds
+ var backgrounds = new powerupjs.GameObjectList(ID.layer_background);
+ var background_main = new powerupjs.SpriteGameObject(sprites.background_sky, ID.layer_background_1);
+ background_main.position = new powerupjs.Vector2(0, powerupjs.Game.size.y - background_main.height);
+ backgrounds.add(background_main);
+
+ for (var i = 0; i < 5; i++) {
+ var sprid = "mountain_" + (Math.ceil(Math.random()*2));
+ var mountain = new powerupjs.SpriteGameObject(sprites[sprid], ID.layer_background_2);
+ mountain.position = new powerupjs.Vector2(Math.random() * powerupjs.Game.size.x - mountain.width / 2,
+ powerupjs.Game.size.y - mountain.height);
+ backgrounds.add(mountain);
+ }
+
+ var clouds = new Clouds(ID.layer_background_3);
+ backgrounds.add(clouds);
+ this.add(backgrounds);
+
+ // timer
+ var timerBackground = new powerupjs.SpriteGameObject(sprites.timer, ID.layer_overlays);
+ timerBackground.position = new powerupjs.Vector2(10, 10);
+ this.add(timerBackground);
+ var timer = new TimerGameObject(ID.layer_overlays_1, ID.timer);
+ timer.position = new powerupjs.Vector2(25, 33);
+ this.add(timer);
+
+ this.loadTiles();
+}
+
+Level.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+Object.defineProperty(Level.prototype, "completed",
+ {
+ get: function () {
+ var player = this.find(ID.player);
+ var exit = this.find(ID.exit);
+ if (!exit.collidesWith(player))
+ return false;
+ for (var i = 0, l = this._waterdrops.length; i < l; ++i) {
+ if (this._waterdrops.at(i).visible)
+ return false;
+ }
+ return true;
+ }
+ });
+
+Object.defineProperty(Level.prototype, "gameOver",
+ {
+ get: function () {
+ var timer = this.find(ID.timer);
+ var player = this.find(ID.player);
+ return !player.alive || timer.gameOver;
+ }
+ });
+
+Level.prototype.loadTiles = function () {
+ var levelData = window.LEVELS[this._levelIndex];
+
+ var hintField = new powerupjs.GameObjectList(ID.layer_overlays);
+ this.add(hintField);
+ var hintFrame = new powerupjs.SpriteGameObject(sprites.frame_hint, ID.layer_overlays_1);
+ hintField.position = new powerupjs.Vector2(hintFrame.screenCenterX, 10);
+ hintField.add(hintFrame);
+ var hintText = new powerupjs.Label("Arial", "14pt", ID.layer_overlays_2);
+ hintText.text = levelData.hint;
+ hintText.position = new powerupjs.Vector2(120, 25);
+ hintText.color = powerupjs.Color.black;
+ hintField.add(hintText);
+ var hintTimer = new VisibilityTimer(hintField, ID.layer_overlays, ID.hint_timer);
+ this.add(hintTimer);
+
+ if (powerupjs.Touch.isTouchDevice) {
+ var walkLeftButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_walkleft);
+ walkLeftButton.position = new powerupjs.Vector2(10, 500);
+ this.add(walkLeftButton);
+ var walkRightButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_walkright);
+ walkRightButton.position = new powerupjs.Vector2(walkRightButton.width + 20, 500);
+ walkRightButton.sheetIndex = 1;
+ this.add(walkRightButton);
+ var jumpButton = new powerupjs.Button(sprites.buttons_player, ID.layer_overlays, ID.button_jump);
+ jumpButton.position = new powerupjs.Vector2(powerupjs.Game.size.x - jumpButton.width - 10, 500);
+ jumpButton.sheetIndex = 2;
+ this.add(jumpButton);
+ }
+
+ var tiles = new TileField(levelData.tiles.length, levelData.tiles[0].length, 1, ID.tiles);
+ this.add(tiles);
+ tiles.cellWidth = 72;
+ tiles.cellHeight = 55;
+ for (var y = 0, ly = tiles.rows; y < ly; ++y)
+ for (var x = 0, lx = tiles.columns; x < lx; ++x) {
+ var t = this.loadTile(levelData.tiles[y][x], x, y);
+ tiles.add(t, x, y);
+ }
+};
+
+Level.prototype.loadTile = function (tileType, x, y) {
+ switch (tileType) {
+ case '.':
+ return new Tile();
+ case '-':
+ return this.loadBasicTile(sprites.platform, TileType.platform);
+ case '+':
+ return this.loadBasicTile(sprites.platform_hot, TileType.platform, true, false);
+ case '@':
+ return this.loadBasicTile(sprites.platform_ice, TileType.platform, false, true);
+ case 'X':
+ return this.loadEndTile(x, y);
+ case 'W':
+ return this.loadWaterTile(x, y);
+ case '1':
+ return this.loadStartTile(x, y);
+ case '#':
+ return this.loadBasicTile(sprites.wall, TileType.normal);
+ case '^':
+ return this.loadBasicTile(sprites.wall_hot, TileType.normal, true, false);
+ case '*':
+ return this.loadBasicTile(sprites.wall_ice, TileType.normal, false, true);
+ case 'R':
+ return this.loadRocketTile(x, y, true);
+ case 'r':
+ return this.loadRocketTile(x, y, false);
+ case 'S':
+ return this.loadSparkyTile(x, y);
+ case 'T':
+ return this.loadTurtleTile(x, y);
+ case 'A':
+ case 'B':
+ case 'C':
+ return this.loadFlameTile(x, y, tileType);
+
+ default:
+ return new Tile();
+ }
+};
+
+Level.prototype.loadBasicTile = function (id, tileType, hot, ice) {
+ var t = new Tile(id, tileType);
+ t.hot = hot;
+ t.ice = ice;
+ return t;
+};
+
+Level.prototype.loadStartTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var startPosition = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ this.add(new Player(startPosition, ID.layer_objects, ID.player));
+ return new Tile();
+};
+
+Level.prototype.loadEndTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var exit = new powerupjs.SpriteGameObject(sprites.goal, ID.layer_objects, ID.exit);
+ exit.position = new powerupjs.Vector2(x * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ exit.origin = new powerupjs.Vector2(0, exit.height);
+ this.add(exit);
+ return new Tile();
+};
+
+Level.prototype.loadWaterTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var w = new WaterDrop(ID.layer_objects);
+ w.origin = w.center.copy();
+ w.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 0.5) * tiles.cellHeight - 10);
+ this._waterdrops.add(w);
+ return new Tile();
+};
+
+Level.prototype.loadRocketTile = function (x, y, moveToLeft) {
+ var tiles = this.find(ID.tiles);
+ var startPosition = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ var enemy = new Rocket(moveToLeft, startPosition, ID.layer_objects);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadTurtleTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var enemy = new Turtle(ID.layer_objects);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight + 25);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadSparkyTile = function (x, y) {
+ var tiles = this.find(ID.tiles);
+ var enemy = new Sparky((y + 1) * tiles.cellHeight - 100, ID.layer_objects);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight - 100);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.loadFlameTile = function (x, y, enemyType) {
+ var enemy = null;
+ switch (enemyType) {
+ case 'A':
+ enemy = new UnpredictableEnemy(ID.layer_objects);
+ break;
+ case 'B':
+ enemy = new PlayerFollowingEnemy(ID.layer_objects);
+ break;
+ case 'C':
+ default:
+ enemy = new PatrollingEnemy(ID.layer_objects);
+ break;
+ }
+ var tiles = this.find(ID.tiles);
+ enemy.position = new powerupjs.Vector2((x + 0.5) * tiles.cellWidth, (y + 1) * tiles.cellHeight);
+ this._enemies.add(enemy);
+ return new Tile();
+};
+
+Level.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ if (!this._quitButton.pressed)
+ return;
+ this.reset();
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+};
+
+Level.prototype.update = function (delta) {
+ powerupjs.GameObjectList.prototype.update.call(this, delta);
+
+ var timer = this.find(ID.timer);
+ var player = this.find(ID.player);
+
+ // check if we died
+ if (!player.alive)
+ timer.running = false;
+
+ // check if we ran out of time
+ if (timer.gameOver)
+ player.explode();
+
+ // check if we won
+ if (this.completed && timer.running) {
+ player.levelFinished();
+ timer.running = false;
+ window.LEVELS[this._levelIndex].solved = true;
+ }
+};
+
+Level.prototype.reset = function () {
+ powerupjs.GameObjectList.prototype.reset.call(this);
+ var hintTimer = this.find(ID.hint_timer);
+ hintTimer.startVisible();
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/states/GameOverState.js b/29_FinishingGame/TickTickFinal/states/GameOverState.js
new file mode 100644
index 0000000..790117f
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/states/GameOverState.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function GameOverState() {
+ powerupjs.GameObjectList.call(this);
+ this.playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ this.overlay = new powerupjs.SpriteGameObject(sprites.overlay_gameover, ID.layer_overlays);
+ this.overlay.position = this.overlay.screenCenter;
+ this.add(this.overlay);
+}
+GameOverState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+GameOverState.prototype.handleInput = function (delta) {
+ if (powerupjs.Touch.isTouchDevice) {
+ if (!powerupjs.Touch.containsTouch(this.overlay.boundingBox))
+ return;
+ }
+ else if (!powerupjs.Mouse.containsMousePress(this.overlay.boundingBox))
+ return;
+ this.playingState.reset();
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+};
+
+GameOverState.prototype.update = function (delta) {
+ this.playingState.update(delta);
+};
+
+GameOverState.prototype.draw = function () {
+ this.playingState.draw();
+ powerupjs.GameObjectList.prototype.draw.call(this);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/states/HelpState.js b/29_FinishingGame/TickTickFinal/states/HelpState.js
new file mode 100644
index 0000000..c71e040
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/states/HelpState.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function HelpState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the background
+ var background = new powerupjs.SpriteGameObject(sprites.background_help, ID.layer_background);
+ this.add(background);
+
+ // add a back button
+ this.backButton = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.backButton.position = new powerupjs.Vector2(this.backButton.screenCenterX, 750);
+ this.add(this.backButton);
+}
+HelpState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+HelpState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.backButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/states/LevelFinishedState.js b/29_FinishingGame/TickTickFinal/states/LevelFinishedState.js
new file mode 100644
index 0000000..0dff11d
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/states/LevelFinishedState.js
@@ -0,0 +1,31 @@
+"use strict";
+
+function LevelFinishedState() {
+ powerupjs.GameObjectList.call(this);
+ this.playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ this.overlay = new powerupjs.SpriteGameObject(sprites.overlay_welldone, ID.layer_overlays);
+ this.overlay.position = this.overlay.screenCenter;
+ this.add(this.overlay);
+}
+
+LevelFinishedState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+LevelFinishedState.prototype.handleInput = function (delta) {
+ if (powerupjs.Touch.isTouchDevice) {
+ if (!powerupjs.Touch.containsTouch(this.overlay.boundingBox))
+ return;
+ }
+ else if (!powerupjs.Mouse.containsMousePress(this.overlay.boundingBox))
+ return;
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+ this.playingState.nextLevel();
+};
+
+LevelFinishedState.prototype.update = function (delta) {
+ this.playingState.update(delta);
+};
+
+LevelFinishedState.prototype.draw = function () {
+ this.playingState.draw();
+ powerupjs.GameObjectList.prototype.draw.call(this);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/states/LevelMenuState.js b/29_FinishingGame/TickTickFinal/states/LevelMenuState.js
new file mode 100644
index 0000000..c00b239
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/states/LevelMenuState.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function LevelMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ this.background = new powerupjs.SpriteGameObject(sprites.levelselect, ID.layer_background);
+ this.add(this.background);
+
+ this.back = new powerupjs.Button(sprites.button_back, ID.layer_overlays);
+ this.back.position = new powerupjs.Vector2(this.back.screenCenterX, 750);
+ this.add(this.back);
+
+ this.levelButtons = [];
+
+ for (var i = 0, j = window.LEVELS.length; i < j; ++i) {
+ var row = Math.floor(i / 4);
+ var column = i % 4;
+ var level = new LevelButton(i, ID.layer_overlays);
+ level.position = new powerupjs.Vector2(column * (level.width + 20) + 390, row * (level.height + 20) + 180);
+ this.add(level);
+ this.levelButtons.push(level);
+ }
+}
+
+LevelMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+LevelMenuState.prototype.getSelectedLevel = function () {
+ for (var i = 0, j = this.levelButtons.length; i < j; i++) {
+ if (this.levelButtons[i].pressed)
+ return this.levelButtons[i].levelIndex;
+ }
+ return -1;
+};
+
+LevelMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+
+ var selectedLevel = this.getSelectedLevel();
+ if (selectedLevel != -1) {
+ var playingState = powerupjs.GameStateManager.get(ID.game_state_playing);
+ playingState.goToLevel(selectedLevel);
+ powerupjs.GameStateManager.switchTo(ID.game_state_playing);
+ }
+ else if (this.back.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_title);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/states/PlayingState.js b/29_FinishingGame/TickTickFinal/states/PlayingState.js
new file mode 100644
index 0000000..1b54c7c
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/states/PlayingState.js
@@ -0,0 +1,75 @@
+"use strict";
+
+function PlayingState() {
+ powerupjs.IGameLoopObject.call(this);
+
+ this.currentLevelIndex = -1;
+ this.levels = [];
+
+ this.loadLevelsStatus();
+ this.loadLevels();
+}
+
+PlayingState.prototype = Object.create(powerupjs.IGameLoopObject.prototype);
+
+Object.defineProperty(PlayingState.prototype, "currentLevel", {
+ get: function () {
+ return this.levels[this.currentLevelIndex];
+ }
+});
+
+PlayingState.prototype.handleInput = function (delta) {
+ this.currentLevel.handleInput(delta);
+};
+
+PlayingState.prototype.update = function (delta) {
+ this.currentLevel.update(delta);
+
+ if (this.currentLevel.gameOver)
+ powerupjs.GameStateManager.switchTo(ID.game_state_gameover);
+ else if (this.currentLevel.completed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelfinished);
+};
+
+PlayingState.prototype.draw = function () {
+ this.currentLevel.draw();
+};
+
+PlayingState.prototype.reset = function () {
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.goToLevel = function (levelIndex) {
+ if (levelIndex < 0 || levelIndex >= this.levels.length)
+ return;
+ this.currentLevelIndex = levelIndex;
+ this.currentLevel.reset();
+};
+
+PlayingState.prototype.nextLevel = function () {
+ if (this.currentLevelIndex >= window.LEVELS.length - 1)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else {
+ this.goToLevel(this.currentLevelIndex + 1);
+ window.LEVELS[this.currentLevelIndex].locked = false;
+ }
+ this.writeLevelsStatus();
+};
+
+PlayingState.prototype.loadLevels = function () {
+ for (var currLevel = 0; currLevel < window.LEVELS.length; currLevel += 1) {
+ this.levels.push(new Level(currLevel));
+ }
+};
+
+PlayingState.prototype.loadLevelsStatus = function () {
+ if (localStorage && localStorage.tickTickLevels) {
+ window.LEVELS = JSON.parse(localStorage.tickTickLevels);
+ }
+};
+
+PlayingState.prototype.writeLevelsStatus = function () {
+ if (!localStorage)
+ return;
+ localStorage.tickTickLevels = JSON.stringify(window.LEVELS);
+};
\ No newline at end of file
diff --git a/29_FinishingGame/TickTickFinal/states/TitleMenuState.js b/29_FinishingGame/TickTickFinal/states/TitleMenuState.js
new file mode 100644
index 0000000..0628cb6
--- /dev/null
+++ b/29_FinishingGame/TickTickFinal/states/TitleMenuState.js
@@ -0,0 +1,29 @@
+"use strict";
+
+function TitleMenuState(layer) {
+ powerupjs.GameObjectList.call(this, layer);
+
+ // the title screen
+ var titleScreen = new powerupjs.SpriteGameObject(sprites.background_title, ID.layer_background);
+ this.add(titleScreen);
+
+ // add a play button
+ this.playButton = new powerupjs.Button(sprites.button_play, ID.layer_overlays);
+ this.playButton.position = new powerupjs.Vector2(this.playButton.screenCenterX, 540);
+ this.add(this.playButton);
+
+ // add a help button
+ this.helpButton = new powerupjs.Button(sprites.button_help, ID.layer_overlays);
+ this.helpButton.position = new powerupjs.Vector2(this.helpButton.screenCenterX, 600);
+ this.add(this.helpButton);
+}
+
+TitleMenuState.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+TitleMenuState.prototype.handleInput = function (delta) {
+ powerupjs.GameObjectList.prototype.handleInput.call(this, delta);
+ if (this.playButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_levelselect);
+ else if (this.helpButton.pressed)
+ powerupjs.GameStateManager.switchTo(ID.game_state_help);
+};
\ No newline at end of file
diff --git a/assets/JewelJam/sounds/snd_combi.mp3 b/assets/JewelJam/sounds/snd_combi.mp3
new file mode 100644
index 0000000..7f8c6a4
Binary files /dev/null and b/assets/JewelJam/sounds/snd_combi.mp3 differ
diff --git a/assets/JewelJam/sounds/snd_combi.ogg b/assets/JewelJam/sounds/snd_combi.ogg
new file mode 100644
index 0000000..806bb35
Binary files /dev/null and b/assets/JewelJam/sounds/snd_combi.ogg differ
diff --git a/assets/JewelJam/sounds/snd_double.mp3 b/assets/JewelJam/sounds/snd_double.mp3
new file mode 100644
index 0000000..7f8c6a4
Binary files /dev/null and b/assets/JewelJam/sounds/snd_double.mp3 differ
diff --git a/assets/JewelJam/sounds/snd_double.ogg b/assets/JewelJam/sounds/snd_double.ogg
new file mode 100644
index 0000000..b218559
Binary files /dev/null and b/assets/JewelJam/sounds/snd_double.ogg differ
diff --git a/assets/JewelJam/sounds/snd_gameover.mp3 b/assets/JewelJam/sounds/snd_gameover.mp3
new file mode 100644
index 0000000..a0751f6
Binary files /dev/null and b/assets/JewelJam/sounds/snd_gameover.mp3 differ
diff --git a/assets/JewelJam/sounds/snd_gameover.ogg b/assets/JewelJam/sounds/snd_gameover.ogg
new file mode 100644
index 0000000..6ca77fa
Binary files /dev/null and b/assets/JewelJam/sounds/snd_gameover.ogg differ
diff --git a/assets/JewelJam/sounds/snd_music.mp3 b/assets/JewelJam/sounds/snd_music.mp3
new file mode 100644
index 0000000..eb9e689
Binary files /dev/null and b/assets/JewelJam/sounds/snd_music.mp3 differ
diff --git a/assets/JewelJam/sounds/snd_music.ogg b/assets/JewelJam/sounds/snd_music.ogg
new file mode 100644
index 0000000..2619d7e
Binary files /dev/null and b/assets/JewelJam/sounds/snd_music.ogg differ
diff --git a/assets/JewelJam/sounds/snd_triple.mp3 b/assets/JewelJam/sounds/snd_triple.mp3
new file mode 100644
index 0000000..185210b
Binary files /dev/null and b/assets/JewelJam/sounds/snd_triple.mp3 differ
diff --git a/assets/JewelJam/sounds/snd_triple.ogg b/assets/JewelJam/sounds/snd_triple.ogg
new file mode 100644
index 0000000..5509fbd
Binary files /dev/null and b/assets/JewelJam/sounds/snd_triple.ogg differ
diff --git a/assets/JewelJam/sprites/spr_background.jpg b/assets/JewelJam/sprites/spr_background.jpg
new file mode 100644
index 0000000..c51bc30
Binary files /dev/null and b/assets/JewelJam/sprites/spr_background.jpg differ
diff --git a/assets/JewelJam/sprites/spr_button_help.jpg b/assets/JewelJam/sprites/spr_button_help.jpg
new file mode 100644
index 0000000..c71d2cc
Binary files /dev/null and b/assets/JewelJam/sprites/spr_button_help.jpg differ
diff --git a/assets/JewelJam/sprites/spr_double.png b/assets/JewelJam/sprites/spr_double.png
new file mode 100644
index 0000000..e80f308
Binary files /dev/null and b/assets/JewelJam/sprites/spr_double.png differ
diff --git a/assets/JewelJam/sprites/spr_frame_help.png b/assets/JewelJam/sprites/spr_frame_help.png
new file mode 100644
index 0000000..cc6e9da
Binary files /dev/null and b/assets/JewelJam/sprites/spr_frame_help.png differ
diff --git a/assets/JewelJam/sprites/spr_frame_score.jpg b/assets/JewelJam/sprites/spr_frame_score.jpg
new file mode 100644
index 0000000..3e19eee
Binary files /dev/null and b/assets/JewelJam/sprites/spr_frame_score.jpg differ
diff --git a/assets/JewelJam/sprites/spr_frame_selector.png b/assets/JewelJam/sprites/spr_frame_selector.png
new file mode 100644
index 0000000..2ec2d52
Binary files /dev/null and b/assets/JewelJam/sprites/spr_frame_selector.png differ
diff --git a/assets/JewelJam/sprites/spr_gameover_click.png b/assets/JewelJam/sprites/spr_gameover_click.png
new file mode 100644
index 0000000..f5163b0
Binary files /dev/null and b/assets/JewelJam/sprites/spr_gameover_click.png differ
diff --git a/assets/JewelJam/sprites/spr_gameover_tap.png b/assets/JewelJam/sprites/spr_gameover_tap.png
new file mode 100644
index 0000000..c54e903
Binary files /dev/null and b/assets/JewelJam/sprites/spr_gameover_tap.png differ
diff --git a/assets/JewelJam/sprites/spr_glitter.png b/assets/JewelJam/sprites/spr_glitter.png
new file mode 100644
index 0000000..e275058
Binary files /dev/null and b/assets/JewelJam/sprites/spr_glitter.png differ
diff --git a/assets/JewelJam/sprites/spr_jewelcart.png b/assets/JewelJam/sprites/spr_jewelcart.png
new file mode 100644
index 0000000..9b1bc46
Binary files /dev/null and b/assets/JewelJam/sprites/spr_jewelcart.png differ
diff --git a/assets/JewelJam/sprites/spr_jewels@27.png b/assets/JewelJam/sprites/spr_jewels@27.png
new file mode 100644
index 0000000..2426222
Binary files /dev/null and b/assets/JewelJam/sprites/spr_jewels@27.png differ
diff --git a/assets/JewelJam/sprites/spr_single_jewel1.png b/assets/JewelJam/sprites/spr_single_jewel1.png
new file mode 100644
index 0000000..09ae7da
Binary files /dev/null and b/assets/JewelJam/sprites/spr_single_jewel1.png differ
diff --git a/assets/JewelJam/sprites/spr_single_jewel2.png b/assets/JewelJam/sprites/spr_single_jewel2.png
new file mode 100644
index 0000000..11a1d1c
Binary files /dev/null and b/assets/JewelJam/sprites/spr_single_jewel2.png differ
diff --git a/assets/JewelJam/sprites/spr_single_jewel3.png b/assets/JewelJam/sprites/spr_single_jewel3.png
new file mode 100644
index 0000000..26e4c9a
Binary files /dev/null and b/assets/JewelJam/sprites/spr_single_jewel3.png differ
diff --git a/assets/JewelJam/sprites/spr_title_click.jpg b/assets/JewelJam/sprites/spr_title_click.jpg
new file mode 100644
index 0000000..a45eea7
Binary files /dev/null and b/assets/JewelJam/sprites/spr_title_click.jpg differ
diff --git a/assets/JewelJam/sprites/spr_title_tap.jpg b/assets/JewelJam/sprites/spr_title_tap.jpg
new file mode 100644
index 0000000..78f8640
Binary files /dev/null and b/assets/JewelJam/sprites/spr_title_tap.jpg differ
diff --git a/assets/JewelJam/sprites/spr_triple.png b/assets/JewelJam/sprites/spr_triple.png
new file mode 100644
index 0000000..5fb4c61
Binary files /dev/null and b/assets/JewelJam/sprites/spr_triple.png differ
diff --git a/assets/Painter/sounds/snd_collect_points.mp3 b/assets/Painter/sounds/snd_collect_points.mp3
new file mode 100644
index 0000000..02c9796
Binary files /dev/null and b/assets/Painter/sounds/snd_collect_points.mp3 differ
diff --git a/assets/Painter/sounds/snd_collect_points.ogg b/assets/Painter/sounds/snd_collect_points.ogg
new file mode 100644
index 0000000..e00de47
Binary files /dev/null and b/assets/Painter/sounds/snd_collect_points.ogg differ
diff --git a/assets/Painter/sounds/snd_music.mp3 b/assets/Painter/sounds/snd_music.mp3
new file mode 100644
index 0000000..2347c04
Binary files /dev/null and b/assets/Painter/sounds/snd_music.mp3 differ
diff --git a/assets/Painter/sounds/snd_music.ogg b/assets/Painter/sounds/snd_music.ogg
new file mode 100644
index 0000000..c52ec30
Binary files /dev/null and b/assets/Painter/sounds/snd_music.ogg differ
diff --git a/assets/Painter/sounds/snd_shoot_paint.mp3 b/assets/Painter/sounds/snd_shoot_paint.mp3
new file mode 100644
index 0000000..de9456c
Binary files /dev/null and b/assets/Painter/sounds/snd_shoot_paint.mp3 differ
diff --git a/assets/Painter/sounds/snd_shoot_paint.ogg b/assets/Painter/sounds/snd_shoot_paint.ogg
new file mode 100644
index 0000000..a2773ff
Binary files /dev/null and b/assets/Painter/sounds/snd_shoot_paint.ogg differ
diff --git a/assets/Painter/sprites/spr_background.jpg b/assets/Painter/sprites/spr_background.jpg
new file mode 100644
index 0000000..9b7cb5f
Binary files /dev/null and b/assets/Painter/sprites/spr_background.jpg differ
diff --git a/assets/Painter/sprites/spr_ball_blue.png b/assets/Painter/sprites/spr_ball_blue.png
new file mode 100644
index 0000000..087cd6d
Binary files /dev/null and b/assets/Painter/sprites/spr_ball_blue.png differ
diff --git a/assets/Painter/sprites/spr_ball_green.png b/assets/Painter/sprites/spr_ball_green.png
new file mode 100644
index 0000000..af20c27
Binary files /dev/null and b/assets/Painter/sprites/spr_ball_green.png differ
diff --git a/assets/Painter/sprites/spr_ball_red.png b/assets/Painter/sprites/spr_ball_red.png
new file mode 100644
index 0000000..2c95334
Binary files /dev/null and b/assets/Painter/sprites/spr_ball_red.png differ
diff --git a/assets/Painter/sprites/spr_can_blue.png b/assets/Painter/sprites/spr_can_blue.png
new file mode 100644
index 0000000..1d8064c
Binary files /dev/null and b/assets/Painter/sprites/spr_can_blue.png differ
diff --git a/assets/Painter/sprites/spr_can_green.png b/assets/Painter/sprites/spr_can_green.png
new file mode 100644
index 0000000..f118a73
Binary files /dev/null and b/assets/Painter/sprites/spr_can_green.png differ
diff --git a/assets/Painter/sprites/spr_can_red.png b/assets/Painter/sprites/spr_can_red.png
new file mode 100644
index 0000000..3b12b00
Binary files /dev/null and b/assets/Painter/sprites/spr_can_red.png differ
diff --git a/assets/Painter/sprites/spr_cannon_barrel.png b/assets/Painter/sprites/spr_cannon_barrel.png
new file mode 100644
index 0000000..a1c43aa
Binary files /dev/null and b/assets/Painter/sprites/spr_cannon_barrel.png differ
diff --git a/assets/Painter/sprites/spr_cannon_blue.png b/assets/Painter/sprites/spr_cannon_blue.png
new file mode 100644
index 0000000..03b5b09
Binary files /dev/null and b/assets/Painter/sprites/spr_cannon_blue.png differ
diff --git a/assets/Painter/sprites/spr_cannon_green.png b/assets/Painter/sprites/spr_cannon_green.png
new file mode 100644
index 0000000..74f6260
Binary files /dev/null and b/assets/Painter/sprites/spr_cannon_green.png differ
diff --git a/assets/Painter/sprites/spr_cannon_red.png b/assets/Painter/sprites/spr_cannon_red.png
new file mode 100644
index 0000000..dcae2cb
Binary files /dev/null and b/assets/Painter/sprites/spr_cannon_red.png differ
diff --git a/assets/Painter/sprites/spr_gameover_click.png b/assets/Painter/sprites/spr_gameover_click.png
new file mode 100644
index 0000000..154198d
Binary files /dev/null and b/assets/Painter/sprites/spr_gameover_click.png differ
diff --git a/assets/Painter/sprites/spr_gameover_tap.png b/assets/Painter/sprites/spr_gameover_tap.png
new file mode 100644
index 0000000..b1fb8ac
Binary files /dev/null and b/assets/Painter/sprites/spr_gameover_tap.png differ
diff --git a/assets/Painter/sprites/spr_lives.png b/assets/Painter/sprites/spr_lives.png
new file mode 100644
index 0000000..b1453b9
Binary files /dev/null and b/assets/Painter/sprites/spr_lives.png differ
diff --git a/assets/Painter/sprites/spr_scorebar.jpg b/assets/Painter/sprites/spr_scorebar.jpg
new file mode 100644
index 0000000..3de325f
Binary files /dev/null and b/assets/Painter/sprites/spr_scorebar.jpg differ
diff --git a/assets/PenguinPairs/levels.js b/assets/PenguinPairs/levels.js
new file mode 100644
index 0000000..70a2103
--- /dev/null
+++ b/assets/PenguinPairs/levels.js
@@ -0,0 +1,228 @@
+window.LEVELS = [];
+
+window.LEVELS.push({
+ locked : false,
+ solved : false,
+ hint : "Click on a penguin and select the arrow to let it move towards the other penguin.",
+ nrPairs : 1,
+ hint_arrow_x : 4,
+ hint_arrow_y : 3,
+ hint_arrow_direction : 3,
+ tiles : ["#########",
+ "#.......#",
+ "#...r...#",
+ "#.......#",
+ "#.......#",
+ "#.......#",
+ "#...r...#",
+ "#.......#",
+ "#########"]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Don't let the penguins fall in the water!",
+ nrPairs : 1,
+ hint_arrow_x : 3,
+ hint_arrow_y : 1,
+ hint_arrow_direction : 2,
+ tiles : ["#.......#",
+ "#...r...#",
+ "#.......#",
+ "#. .#",
+ "#. .#",
+ "#. .#",
+ "#.......#",
+ "#...r...#",
+ "#.......#"]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "The order in which you move the penguins matters.",
+ nrPairs : 1,
+ hint_arrow_x : 8,
+ hint_arrow_y : 1,
+ hint_arrow_direction : 2,
+ tiles : [" #######",
+ " #....r.",
+ " #.#####",
+ " #.# ",
+ " #.# ",
+ "#####.#####",
+ ".r.........",
+ "###########"]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Create multiple pairs. The number of required pairs is indicated at the top.",
+ nrPairs : 5,
+ hint_arrow_x : 2,
+ hint_arrow_y : 1,
+ hint_arrow_direction : 3,
+ tiles : ["..y....",
+ ".y...y.",
+ ".......",
+ ".y...y.",
+ "..y.y..",
+ ".......",
+ ".y.....",
+ ".....y.",
+ "..y...."]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Pair each penguin with a similar one.",
+ nrPairs : 2,
+ hint_arrow_x : 5,
+ hint_arrow_y : 6,
+ hint_arrow_direction : 1,
+ tiles : [".......",
+ ".b...b.",
+ ".......",
+ ".r.....",
+ ".......",
+ ".......",
+ ".......",
+ ".....r.",
+ "......."]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Animals sometimes must be sacrificed to arrange all required pairs.",
+ nrPairs : 2,
+ hint_arrow_x : 1,
+ hint_arrow_y : 4,
+ hint_arrow_direction : 2,
+ tiles : [" ... ",
+ "..r.. ",
+ "..... ",
+ "..b..*",
+ "..g.. ",
+ "..b..*",
+ "..... ",
+ "..r.. ",
+ " ... "]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Multi-colored penguins pair with any penguin.",
+ nrPairs : 2,
+ hint_arrow_x : 4,
+ hint_arrow_y : 3,
+ hint_arrow_direction : 0,
+ tiles : [" ... ",
+ " ..r.. ",
+ " ..... ",
+ " ..b..#",
+ "#..m.. ",
+ " ..b..#",
+ " ..... ",
+ " ..g.. ",
+ " ... "]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Use the seals to stop the penguins from moving.",
+ nrPairs : 1,
+ hint_arrow_x : 5,
+ hint_arrow_y : 4,
+ hint_arrow_direction : 1,
+ tiles : [".......",
+ ".....x.",
+ ".x.x...",
+ ".......",
+ "...x...",
+ ".....b.",
+ ".......",
+ "..b....",
+ "......."]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Use all the penguins to pair the blue penguins.",
+ nrPairs : 1,
+ hint_arrow_x : 4,
+ hint_arrow_y : 1,
+ hint_arrow_direction : 2,
+ tiles : [".......",
+ ".g...r.",
+ ".......",
+ ".....b.",
+ ".......",
+ ".y...p.",
+ ".......",
+ "...b...",
+ "......."]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "The penguin in the hole cannot move.",
+ nrPairs : 1,
+ hint_arrow_x : 5,
+ hint_arrow_y : 2,
+ hint_arrow_direction : 1,
+ tiles : [".......",
+ ".....x.",
+ ".x.....",
+ ".....x.",
+ ".......",
+ ".x...r.",
+ "..x....",
+ "...R...",
+ "......."]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Catch the penguin in the hole.",
+ nrPairs : 1,
+ hint_arrow_x : 5,
+ hint_arrow_y : 4,
+ hint_arrow_direction : 3,
+ tiles : [".......",
+ ".......",
+ ".......",
+ ".r..Xx.",
+ ".......",
+ ".......",
+ ".......",
+ "...r...",
+ ".....x."]
+});
+
+window.LEVELS.push({
+ locked : true,
+ solved : false,
+ hint : "Use the seals to get the sharks out of the way in order to make the pair.",
+ nrPairs : 1,
+ hint_arrow_x : 5,
+ hint_arrow_y : 4,
+ hint_arrow_direction : 3,
+ tiles : [".......",
+ "...p...",
+ ".......",
+ ".x.@.x.",
+ "...@...",
+ "...@...",
+ ".@...x.",
+ ".x.p...",
+ "......."]
+});
\ No newline at end of file
diff --git a/assets/PenguinPairs/sounds/snd_eat.mp3 b/assets/PenguinPairs/sounds/snd_eat.mp3
new file mode 100644
index 0000000..d820876
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_eat.mp3 differ
diff --git a/assets/PenguinPairs/sounds/snd_eat.ogg b/assets/PenguinPairs/sounds/snd_eat.ogg
new file mode 100644
index 0000000..0fe0ed8
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_eat.ogg differ
diff --git a/assets/PenguinPairs/sounds/snd_lost.mp3 b/assets/PenguinPairs/sounds/snd_lost.mp3
new file mode 100644
index 0000000..a0751f6
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_lost.mp3 differ
diff --git a/assets/PenguinPairs/sounds/snd_lost.ogg b/assets/PenguinPairs/sounds/snd_lost.ogg
new file mode 100644
index 0000000..86d3fb2
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_lost.ogg differ
diff --git a/assets/PenguinPairs/sounds/snd_music.mp3 b/assets/PenguinPairs/sounds/snd_music.mp3
new file mode 100644
index 0000000..96a688b
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_music.mp3 differ
diff --git a/assets/PenguinPairs/sounds/snd_music.ogg b/assets/PenguinPairs/sounds/snd_music.ogg
new file mode 100644
index 0000000..cf5d863
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_music.ogg differ
diff --git a/assets/PenguinPairs/sounds/snd_pair.mp3 b/assets/PenguinPairs/sounds/snd_pair.mp3
new file mode 100644
index 0000000..7f8c6a4
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_pair.mp3 differ
diff --git a/assets/PenguinPairs/sounds/snd_pair.ogg b/assets/PenguinPairs/sounds/snd_pair.ogg
new file mode 100644
index 0000000..b602158
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_pair.ogg differ
diff --git a/assets/PenguinPairs/sounds/snd_won.mp3 b/assets/PenguinPairs/sounds/snd_won.mp3
new file mode 100644
index 0000000..185210b
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_won.mp3 differ
diff --git a/assets/PenguinPairs/sounds/snd_won.ogg b/assets/PenguinPairs/sounds/snd_won.ogg
new file mode 100644
index 0000000..7ee19f7
Binary files /dev/null and b/assets/PenguinPairs/sounds/snd_won.ogg differ
diff --git a/assets/PenguinPairs/sprites/spr_arrow@4.png b/assets/PenguinPairs/sprites/spr_arrow@4.png
new file mode 100644
index 0000000..2c67f31
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_arrow@4.png differ
diff --git a/assets/PenguinPairs/sprites/spr_arrow_hint@4.png b/assets/PenguinPairs/sprites/spr_arrow_hint@4.png
new file mode 100644
index 0000000..44ffcc1
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_arrow_hint@4.png differ
diff --git a/assets/PenguinPairs/sprites/spr_arrow_hover@4.png b/assets/PenguinPairs/sprites/spr_arrow_hover@4.png
new file mode 100644
index 0000000..f623c43
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_arrow_hover@4.png differ
diff --git a/assets/PenguinPairs/sprites/spr_background_help.jpg b/assets/PenguinPairs/sprites/spr_background_help.jpg
new file mode 100644
index 0000000..bf41e5f
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_background_help.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_background_level.jpg b/assets/PenguinPairs/sprites/spr_background_level.jpg
new file mode 100644
index 0000000..415d9a6
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_background_level.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_background_levelselect.jpg b/assets/PenguinPairs/sprites/spr_background_levelselect.jpg
new file mode 100644
index 0000000..3490b0e
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_background_levelselect.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_background_options.jpg b/assets/PenguinPairs/sprites/spr_background_options.jpg
new file mode 100644
index 0000000..1ac8b18
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_background_options.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_background_title.jpg b/assets/PenguinPairs/sprites/spr_background_title.jpg
new file mode 100644
index 0000000..394bda9
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_background_title.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_button_back.jpg b/assets/PenguinPairs/sprites/spr_button_back.jpg
new file mode 100644
index 0000000..7757076
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_button_back.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_button_help.jpg b/assets/PenguinPairs/sprites/spr_button_help.jpg
new file mode 100644
index 0000000..d2f61e3
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_button_help.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_button_hint.png b/assets/PenguinPairs/sprites/spr_button_hint.png
new file mode 100644
index 0000000..2cf4c7f
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_button_hint.png differ
diff --git a/assets/PenguinPairs/sprites/spr_button_offon@2.png b/assets/PenguinPairs/sprites/spr_button_offon@2.png
new file mode 100644
index 0000000..c576aed
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_button_offon@2.png differ
diff --git a/assets/PenguinPairs/sprites/spr_button_options.jpg b/assets/PenguinPairs/sprites/spr_button_options.jpg
new file mode 100644
index 0000000..2f9a0ef
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_button_options.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_button_play.jpg b/assets/PenguinPairs/sprites/spr_button_play.jpg
new file mode 100644
index 0000000..ff2822b
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_button_play.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_button_quit.png b/assets/PenguinPairs/sprites/spr_button_quit.png
new file mode 100644
index 0000000..feb7f61
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_button_quit.png differ
diff --git a/assets/PenguinPairs/sprites/spr_button_retry.png b/assets/PenguinPairs/sprites/spr_button_retry.png
new file mode 100644
index 0000000..b20e268
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_button_retry.png differ
diff --git a/assets/PenguinPairs/sprites/spr_field@2.png b/assets/PenguinPairs/sprites/spr_field@2.png
new file mode 100644
index 0000000..9811900
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_field@2.png differ
diff --git a/assets/PenguinPairs/sprites/spr_frame_goal.jpg b/assets/PenguinPairs/sprites/spr_frame_goal.jpg
new file mode 100644
index 0000000..60ee73c
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_frame_goal.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_help.jpg b/assets/PenguinPairs/sprites/spr_help.jpg
new file mode 100644
index 0000000..a2cffa7
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_help.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_level_finished_click.png b/assets/PenguinPairs/sprites/spr_level_finished_click.png
new file mode 100644
index 0000000..7ecbef5
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_level_finished_click.png differ
diff --git a/assets/PenguinPairs/sprites/spr_level_finished_tap.png b/assets/PenguinPairs/sprites/spr_level_finished_tap.png
new file mode 100644
index 0000000..0074d9d
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_level_finished_tap.png differ
diff --git a/assets/PenguinPairs/sprites/spr_level_solved@6.png b/assets/PenguinPairs/sprites/spr_level_solved@6.png
new file mode 100644
index 0000000..c78493c
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_level_solved@6.png differ
diff --git a/assets/PenguinPairs/sprites/spr_level_unsolved.png b/assets/PenguinPairs/sprites/spr_level_unsolved.png
new file mode 100644
index 0000000..cef6f3f
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_level_unsolved.png differ
diff --git a/assets/PenguinPairs/sprites/spr_lock.png b/assets/PenguinPairs/sprites/spr_lock.png
new file mode 100644
index 0000000..ec1dc38
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_lock.png differ
diff --git a/assets/PenguinPairs/sprites/spr_penguin@4x2.png b/assets/PenguinPairs/sprites/spr_penguin@4x2.png
new file mode 100644
index 0000000..fb36169
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_penguin@4x2.png differ
diff --git a/assets/PenguinPairs/sprites/spr_penguin@8.png b/assets/PenguinPairs/sprites/spr_penguin@8.png
new file mode 100644
index 0000000..751be2f
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_penguin@8.png differ
diff --git a/assets/PenguinPairs/sprites/spr_penguin_boxed@8.png b/assets/PenguinPairs/sprites/spr_penguin_boxed@8.png
new file mode 100644
index 0000000..d189d81
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_penguin_boxed@8.png differ
diff --git a/assets/PenguinPairs/sprites/spr_penguin_empty.png b/assets/PenguinPairs/sprites/spr_penguin_empty.png
new file mode 100644
index 0000000..e4eb07d
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_penguin_empty.png differ
diff --git a/assets/PenguinPairs/sprites/spr_penguin_pairs@8.png b/assets/PenguinPairs/sprites/spr_penguin_pairs@8.png
new file mode 100644
index 0000000..d57c405
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_penguin_pairs@8.png differ
diff --git a/assets/PenguinPairs/sprites/spr_shark.png b/assets/PenguinPairs/sprites/spr_shark.png
new file mode 100644
index 0000000..677ce42
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_shark.png differ
diff --git a/assets/PenguinPairs/sprites/spr_slider_bar.jpg b/assets/PenguinPairs/sprites/spr_slider_bar.jpg
new file mode 100644
index 0000000..5d569e0
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_slider_bar.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_slider_button.jpg b/assets/PenguinPairs/sprites/spr_slider_button.jpg
new file mode 100644
index 0000000..9a30ff2
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_slider_button.jpg differ
diff --git a/assets/PenguinPairs/sprites/spr_wall.png b/assets/PenguinPairs/sprites/spr_wall.png
new file mode 100644
index 0000000..dae7577
Binary files /dev/null and b/assets/PenguinPairs/sprites/spr_wall.png differ
diff --git a/assets/TickTick/levels.js b/assets/TickTick/levels.js
new file mode 100644
index 0000000..7a0f95b
--- /dev/null
+++ b/assets/TickTick/levels.js
@@ -0,0 +1,211 @@
+window.LEVELS = [];
+
+window.LEVELS.push({
+ hint : "Pick up all the waterdrops and reach the exit in time.",
+ locked : false,
+ solved : false,
+ tiles : ["....................",
+ ".................X..",
+ "..........##########",
+ "....................",
+ "WWW....WWWW.........",
+ "---....####.........",
+ "....................",
+ "WWW.................",
+ "###.........WWWWW...",
+ "............#####...",
+ "....WWW.............",
+ "....###.............",
+ "....................",
+ ".1........W.W.W.W.W.",
+ "####################"]
+});
+
+window.LEVELS.push({
+ hint : "Watch out, the ice is slippery!",
+ locked : true,
+ solved : false,
+ tiles : ["....................",
+ ".......W....W.......",
+ "***.................",
+ "......***..***......",
+ "1..................X",
+ "****............****",
+ "....................",
+ "W.W.W..........W.W.W",
+ "*****..........*****",
+ "....................",
+ "W.W.W..........W.W.W",
+ "******........******",
+ "....................",
+ "...W.W........W.W...",
+ "...****......****..."]
+});
+
+window.LEVELS.push({
+ hint : "Avoid the rockets, you can jump through some of the tiles.",
+ locked : true,
+ solved : false,
+ tiles : ["....................",
+ "r...............X...",
+ ".----..W.......--..W",
+ "....W.--..........##",
+ "...--....-----......",
+ "r................W.W",
+ ".......W....W..#####",
+ "....W..######.......",
+ "....................",
+ "-----..........-----",
+ "....................",
+ "W.W.............W..R",
+ "---............-----",
+ ".1...W..W..W..W.W.W.",
+ "###..#..#..#..######"]
+});
+
+window.LEVELS.push({
+ hint : "Red tiles are hot and reduce the time before you explode.",
+ locked : true,
+ solved : false,
+ tiles : ["........W...W.......",
+ ".X....W...W...W....R",
+ "####^^^^^^^^^^^^^^..",
+ ".......W...W...W....",
+ "r....W...W...W...W..",
+ "..^^^^^^^^^^^^^^^^##",
+ "....W...W...W.......",
+ "..W...W...W...W....R",
+ "##^^^^^^^^^^^^^^^^..",
+ ".......W...W...W....",
+ "r....W...W...W...W..",
+ "..^^^^^^^^^^^^^^^^##",
+ "...W...W...W...W....",
+ ".W...W...W...W....1.",
+ "####################"]
+});
+
+window.LEVELS.push({
+ hint : "Do not touch the flames, and watch how they move.",
+ locked : true,
+ solved : false,
+ tiles : ["....................",
+ "....................",
+ "..........X.........",
+ ".......######.......",
+ "..W..............W..",
+ "####..W.W.W.W....###",
+ ".......W.W.WBW......",
+ "......++++++++......",
+ "....-...........-...",
+ "....................",
+ "W..W............W.W.",
+ "####............####",
+ "....................",
+ ".1.........A........",
+ "#######..***********"]
+});
+
+window.LEVELS.push({
+ hint : "Many, many, many, many, many rockets...",
+ locked : true,
+ solved : false,
+ tiles : ["....................",
+ "r..W...........X....",
+ "...--..W.......--...",
+ "....W.--........W..R",
+ "...--..........--...",
+ "r..W......W....W....",
+ "...--....--....--...",
+ "....W...........W...",
+ "...--........W.--...",
+ "r..W........--.W....",
+ "...--..........--...",
+ "....W...........W..R",
+ "...--..........--...",
+ ".1..................",
+ "######..####..######"]
+});
+
+window.LEVELS.push({
+ hint : "Use the turtle to jump higher, but watch out when it sneezes.",
+ locked : true,
+ solved : false,
+ tiles : ["....................",
+ "r..W.W.W.......W.W.W",
+ "@@@@@@@@............",
+ ".............---....",
+ "....................",
+ "........---.......X.",
+ "................----",
+ "........C...........",
+ "......-------.......",
+ "................W..R",
+ "...............++...",
+ "W..T..........W.....",
+ "+++++.........++....",
+ ".1..............W...",
+ "#######......@@@@@.."]
+});
+
+window.LEVELS.push({
+ hint : "When Sparky drops, get out of the way!",
+ locked : true,
+ solved : false,
+ tiles : ["....................",
+ "..........X.........",
+ "........-----.......",
+ "....................",
+ "..C...W.W.W.W.W..C..",
+ "..----------------..",
+ ".W..................",
+ "---.W...........W...",
+ "....-...........-...",
+ "....................",
+ ".S..W.....W......S.W",
+ "---.............----",
+ "....................",
+ ".1.....S.....W......",
+ "###...###...###....."]
+});
+
+window.LEVELS.push({
+ hint : "There are many drops in the ocean...",
+ locked : true,
+ solved : false,
+ tiles : ["....................",
+ "r....W..............",
+ "...@@@..............",
+ "...S..........WWW..R",
+ "...@@.........WWW...",
+ "..........1...WWW...",
+ "...@@....@@...WWW...",
+ "....W.........@@@...",
+ "............. WWW..R",
+ "...@@.......--WWW...",
+ "..............WWW...",
+ "r...........--WWW...",
+ "..............WWW...",
+ ".X..A...T.....WWW...",
+ "######..###...@@@..."]
+});
+
+window.LEVELS.push({
+ hint : "Six levels of hot flames...",
+ locked : true,
+ solved : false,
+ tiles : ["..........W........R",
+ "A...W....###........",
+ "+++++++...1.........",
+ "r.........W......S.X",
+ "C...W.....W......###",
+ "+++++++...W.........",
+ "..........W........R",
+ "..C.W.....W.........",
+ "+++++++...W..W....C.",
+ "..........W..+++++++",
+ "r..CW.....W.........",
+ "+++++++...W..W.....C",
+ "..........W..+++++++",
+ "..........W........R",
+ "..@@@@@@@@@@@@@@@@.."]
+});
\ No newline at end of file
diff --git a/assets/TickTick/sounds/snd_music.mp3 b/assets/TickTick/sounds/snd_music.mp3
new file mode 100644
index 0000000..f90bccb
Binary files /dev/null and b/assets/TickTick/sounds/snd_music.mp3 differ
diff --git a/assets/TickTick/sounds/snd_music.ogg b/assets/TickTick/sounds/snd_music.ogg
new file mode 100644
index 0000000..84790f3
Binary files /dev/null and b/assets/TickTick/sounds/snd_music.ogg differ
diff --git a/assets/TickTick/sounds/snd_player_die.mp3 b/assets/TickTick/sounds/snd_player_die.mp3
new file mode 100644
index 0000000..a1c38d5
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_die.mp3 differ
diff --git a/assets/TickTick/sounds/snd_player_die.ogg b/assets/TickTick/sounds/snd_player_die.ogg
new file mode 100644
index 0000000..aea48b1
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_die.ogg differ
diff --git a/assets/TickTick/sounds/snd_player_explode.mp3 b/assets/TickTick/sounds/snd_player_explode.mp3
new file mode 100644
index 0000000..d820876
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_explode.mp3 differ
diff --git a/assets/TickTick/sounds/snd_player_explode.ogg b/assets/TickTick/sounds/snd_player_explode.ogg
new file mode 100644
index 0000000..0fe0ed8
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_explode.ogg differ
diff --git a/assets/TickTick/sounds/snd_player_fall.mp3 b/assets/TickTick/sounds/snd_player_fall.mp3
new file mode 100644
index 0000000..7278c96
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_fall.mp3 differ
diff --git a/assets/TickTick/sounds/snd_player_fall.ogg b/assets/TickTick/sounds/snd_player_fall.ogg
new file mode 100644
index 0000000..05414d5
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_fall.ogg differ
diff --git a/assets/TickTick/sounds/snd_player_jump.mp3 b/assets/TickTick/sounds/snd_player_jump.mp3
new file mode 100644
index 0000000..600e266
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_jump.mp3 differ
diff --git a/assets/TickTick/sounds/snd_player_jump.ogg b/assets/TickTick/sounds/snd_player_jump.ogg
new file mode 100644
index 0000000..4bf927b
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_jump.ogg differ
diff --git a/assets/TickTick/sounds/snd_player_won.mp3 b/assets/TickTick/sounds/snd_player_won.mp3
new file mode 100644
index 0000000..ef77bb9
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_won.mp3 differ
diff --git a/assets/TickTick/sounds/snd_player_won.ogg b/assets/TickTick/sounds/snd_player_won.ogg
new file mode 100644
index 0000000..9ec26bf
Binary files /dev/null and b/assets/TickTick/sounds/snd_player_won.ogg differ
diff --git a/assets/TickTick/sounds/snd_water_collected.mp3 b/assets/TickTick/sounds/snd_water_collected.mp3
new file mode 100644
index 0000000..6496561
Binary files /dev/null and b/assets/TickTick/sounds/snd_water_collected.mp3 differ
diff --git a/assets/TickTick/sounds/snd_water_collected.ogg b/assets/TickTick/sounds/snd_water_collected.ogg
new file mode 100644
index 0000000..8aa7084
Binary files /dev/null and b/assets/TickTick/sounds/snd_water_collected.ogg differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_cloud_1.png b/assets/TickTick/sprites/backgrounds/spr_cloud_1.png
new file mode 100644
index 0000000..5ba9846
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_cloud_1.png differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_cloud_2.png b/assets/TickTick/sprites/backgrounds/spr_cloud_2.png
new file mode 100644
index 0000000..6940d11
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_cloud_2.png differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_cloud_3.png b/assets/TickTick/sprites/backgrounds/spr_cloud_3.png
new file mode 100644
index 0000000..3e702d1
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_cloud_3.png differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_cloud_4.png b/assets/TickTick/sprites/backgrounds/spr_cloud_4.png
new file mode 100644
index 0000000..bf58ba3
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_cloud_4.png differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_cloud_5.png b/assets/TickTick/sprites/backgrounds/spr_cloud_5.png
new file mode 100644
index 0000000..7ac217c
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_cloud_5.png differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_help.jpg b/assets/TickTick/sprites/backgrounds/spr_help.jpg
new file mode 100644
index 0000000..ba1827e
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_help.jpg differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_levelselect.jpg b/assets/TickTick/sprites/backgrounds/spr_levelselect.jpg
new file mode 100644
index 0000000..5a80c45
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_levelselect.jpg differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_mountain_1.png b/assets/TickTick/sprites/backgrounds/spr_mountain_1.png
new file mode 100644
index 0000000..610bbed
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_mountain_1.png differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_mountain_2.png b/assets/TickTick/sprites/backgrounds/spr_mountain_2.png
new file mode 100644
index 0000000..a01b671
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_mountain_2.png differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_sky.jpg b/assets/TickTick/sprites/backgrounds/spr_sky.jpg
new file mode 100644
index 0000000..47abb5d
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_sky.jpg differ
diff --git a/assets/TickTick/sprites/backgrounds/spr_title.jpg b/assets/TickTick/sprites/backgrounds/spr_title.jpg
new file mode 100644
index 0000000..5d4fb07
Binary files /dev/null and b/assets/TickTick/sprites/backgrounds/spr_title.jpg differ
diff --git a/assets/TickTick/sprites/flame/spr_flame@9.png b/assets/TickTick/sprites/flame/spr_flame@9.png
new file mode 100644
index 0000000..388c781
Binary files /dev/null and b/assets/TickTick/sprites/flame/spr_flame@9.png differ
diff --git a/assets/TickTick/sprites/gui/spr_button_back.png b/assets/TickTick/sprites/gui/spr_button_back.png
new file mode 100644
index 0000000..c15efe8
Binary files /dev/null and b/assets/TickTick/sprites/gui/spr_button_back.png differ
diff --git a/assets/TickTick/sprites/gui/spr_button_help.png b/assets/TickTick/sprites/gui/spr_button_help.png
new file mode 100644
index 0000000..ec519a8
Binary files /dev/null and b/assets/TickTick/sprites/gui/spr_button_help.png differ
diff --git a/assets/TickTick/sprites/gui/spr_button_play.png b/assets/TickTick/sprites/gui/spr_button_play.png
new file mode 100644
index 0000000..8c8e328
Binary files /dev/null and b/assets/TickTick/sprites/gui/spr_button_play.png differ
diff --git a/assets/TickTick/sprites/gui/spr_button_quit.png b/assets/TickTick/sprites/gui/spr_button_quit.png
new file mode 100644
index 0000000..69767b3
Binary files /dev/null and b/assets/TickTick/sprites/gui/spr_button_quit.png differ
diff --git a/assets/TickTick/sprites/gui/spr_buttons_player@3.png b/assets/TickTick/sprites/gui/spr_buttons_player@3.png
new file mode 100644
index 0000000..dbdb65a
Binary files /dev/null and b/assets/TickTick/sprites/gui/spr_buttons_player@3.png differ
diff --git a/assets/TickTick/sprites/gui/spr_level_locked.png b/assets/TickTick/sprites/gui/spr_level_locked.png
new file mode 100644
index 0000000..4946e20
Binary files /dev/null and b/assets/TickTick/sprites/gui/spr_level_locked.png differ
diff --git a/assets/TickTick/sprites/gui/spr_level_solved.png b/assets/TickTick/sprites/gui/spr_level_solved.png
new file mode 100644
index 0000000..2136c26
Binary files /dev/null and b/assets/TickTick/sprites/gui/spr_level_solved.png differ
diff --git a/assets/TickTick/sprites/gui/spr_level_unsolved.png b/assets/TickTick/sprites/gui/spr_level_unsolved.png
new file mode 100644
index 0000000..a1fe7ed
Binary files /dev/null and b/assets/TickTick/sprites/gui/spr_level_unsolved.png differ
diff --git a/assets/TickTick/sprites/overlays/spr_frame_hint.png b/assets/TickTick/sprites/overlays/spr_frame_hint.png
new file mode 100644
index 0000000..24af1ad
Binary files /dev/null and b/assets/TickTick/sprites/overlays/spr_frame_hint.png differ
diff --git a/assets/TickTick/sprites/overlays/spr_gameover_click.png b/assets/TickTick/sprites/overlays/spr_gameover_click.png
new file mode 100644
index 0000000..a724670
Binary files /dev/null and b/assets/TickTick/sprites/overlays/spr_gameover_click.png differ
diff --git a/assets/TickTick/sprites/overlays/spr_gameover_tap.png b/assets/TickTick/sprites/overlays/spr_gameover_tap.png
new file mode 100644
index 0000000..6a93d85
Binary files /dev/null and b/assets/TickTick/sprites/overlays/spr_gameover_tap.png differ
diff --git a/assets/TickTick/sprites/overlays/spr_timer.png b/assets/TickTick/sprites/overlays/spr_timer.png
new file mode 100644
index 0000000..0331d5f
Binary files /dev/null and b/assets/TickTick/sprites/overlays/spr_timer.png differ
diff --git a/assets/TickTick/sprites/overlays/spr_welldone_click.png b/assets/TickTick/sprites/overlays/spr_welldone_click.png
new file mode 100644
index 0000000..bb984ac
Binary files /dev/null and b/assets/TickTick/sprites/overlays/spr_welldone_click.png differ
diff --git a/assets/TickTick/sprites/overlays/spr_welldone_tap.png b/assets/TickTick/sprites/overlays/spr_welldone_tap.png
new file mode 100644
index 0000000..2e794f3
Binary files /dev/null and b/assets/TickTick/sprites/overlays/spr_welldone_tap.png differ
diff --git a/assets/TickTick/sprites/player/spr_celebrate@14.png b/assets/TickTick/sprites/player/spr_celebrate@14.png
new file mode 100644
index 0000000..905d126
Binary files /dev/null and b/assets/TickTick/sprites/player/spr_celebrate@14.png differ
diff --git a/assets/TickTick/sprites/player/spr_die@5.png b/assets/TickTick/sprites/player/spr_die@5.png
new file mode 100644
index 0000000..d38745b
Binary files /dev/null and b/assets/TickTick/sprites/player/spr_die@5.png differ
diff --git a/assets/TickTick/sprites/player/spr_explode@5x5.png b/assets/TickTick/sprites/player/spr_explode@5x5.png
new file mode 100644
index 0000000..3e107f2
Binary files /dev/null and b/assets/TickTick/sprites/player/spr_explode@5x5.png differ
diff --git a/assets/TickTick/sprites/player/spr_idle.png b/assets/TickTick/sprites/player/spr_idle.png
new file mode 100644
index 0000000..0d10bfe
Binary files /dev/null and b/assets/TickTick/sprites/player/spr_idle.png differ
diff --git a/assets/TickTick/sprites/player/spr_jump@14.png b/assets/TickTick/sprites/player/spr_jump@14.png
new file mode 100644
index 0000000..4061140
Binary files /dev/null and b/assets/TickTick/sprites/player/spr_jump@14.png differ
diff --git a/assets/TickTick/sprites/player/spr_run@13.png b/assets/TickTick/sprites/player/spr_run@13.png
new file mode 100644
index 0000000..b2f6e34
Binary files /dev/null and b/assets/TickTick/sprites/player/spr_run@13.png differ
diff --git a/assets/TickTick/sprites/rocket/spr_rocket@3.png b/assets/TickTick/sprites/rocket/spr_rocket@3.png
new file mode 100644
index 0000000..122eec8
Binary files /dev/null and b/assets/TickTick/sprites/rocket/spr_rocket@3.png differ
diff --git a/assets/TickTick/sprites/sparky/spr_electrocute.png b/assets/TickTick/sprites/sparky/spr_electrocute.png
new file mode 100644
index 0000000..1a123dc
Binary files /dev/null and b/assets/TickTick/sprites/sparky/spr_electrocute.png differ
diff --git a/assets/TickTick/sprites/sparky/spr_electrocute@6x5.png b/assets/TickTick/sprites/sparky/spr_electrocute@6x5.png
new file mode 100644
index 0000000..5a63819
Binary files /dev/null and b/assets/TickTick/sprites/sparky/spr_electrocute@6x5.png differ
diff --git a/assets/TickTick/sprites/sparky/spr_idle.png b/assets/TickTick/sprites/sparky/spr_idle.png
new file mode 100644
index 0000000..03a457c
Binary files /dev/null and b/assets/TickTick/sprites/sparky/spr_idle.png differ
diff --git a/assets/TickTick/sprites/spr_goal.png b/assets/TickTick/sprites/spr_goal.png
new file mode 100644
index 0000000..c0aa2ee
Binary files /dev/null and b/assets/TickTick/sprites/spr_goal.png differ
diff --git a/assets/TickTick/sprites/spr_water.png b/assets/TickTick/sprites/spr_water.png
new file mode 100644
index 0000000..26808cf
Binary files /dev/null and b/assets/TickTick/sprites/spr_water.png differ
diff --git a/assets/TickTick/sprites/tiles/spr_platform.png b/assets/TickTick/sprites/tiles/spr_platform.png
new file mode 100644
index 0000000..be1be43
Binary files /dev/null and b/assets/TickTick/sprites/tiles/spr_platform.png differ
diff --git a/assets/TickTick/sprites/tiles/spr_platform_hot.png b/assets/TickTick/sprites/tiles/spr_platform_hot.png
new file mode 100644
index 0000000..dbc0321
Binary files /dev/null and b/assets/TickTick/sprites/tiles/spr_platform_hot.png differ
diff --git a/assets/TickTick/sprites/tiles/spr_platform_ice.png b/assets/TickTick/sprites/tiles/spr_platform_ice.png
new file mode 100644
index 0000000..95e43da
Binary files /dev/null and b/assets/TickTick/sprites/tiles/spr_platform_ice.png differ
diff --git a/assets/TickTick/sprites/tiles/spr_wall.png b/assets/TickTick/sprites/tiles/spr_wall.png
new file mode 100644
index 0000000..bf2957e
Binary files /dev/null and b/assets/TickTick/sprites/tiles/spr_wall.png differ
diff --git a/assets/TickTick/sprites/tiles/spr_wall_hot.png b/assets/TickTick/sprites/tiles/spr_wall_hot.png
new file mode 100644
index 0000000..1cbcdba
Binary files /dev/null and b/assets/TickTick/sprites/tiles/spr_wall_hot.png differ
diff --git a/assets/TickTick/sprites/tiles/spr_wall_ice.png b/assets/TickTick/sprites/tiles/spr_wall_ice.png
new file mode 100644
index 0000000..331e6dd
Binary files /dev/null and b/assets/TickTick/sprites/tiles/spr_wall_ice.png differ
diff --git a/assets/TickTick/sprites/turtle/spr_idle.png b/assets/TickTick/sprites/turtle/spr_idle.png
new file mode 100644
index 0000000..68995c1
Binary files /dev/null and b/assets/TickTick/sprites/turtle/spr_idle.png differ
diff --git a/assets/TickTick/sprites/turtle/spr_sneeze@9.png b/assets/TickTick/sprites/turtle/spr_sneeze@9.png
new file mode 100644
index 0000000..96f8998
Binary files /dev/null and b/assets/TickTick/sprites/turtle/spr_sneeze@9.png differ
diff --git a/powerupjs/Animation.js b/powerupjs/Animation.js
new file mode 100644
index 0000000..a4760d5
--- /dev/null
+++ b/powerupjs/Animation.js
@@ -0,0 +1,14 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function Animation(sprite, looping, frameTime) {
+ this.sprite = sprite;
+ this.frameTime = typeof frameTime != 'undefined' ? frameTime : 0.1;
+ this.looping = looping;
+ }
+
+ powerupjs.Animation = Animation;
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/Canvas2D.js b/powerupjs/Canvas2D.js
new file mode 100644
index 0000000..d3376f6
--- /dev/null
+++ b/powerupjs/Canvas2D.js
@@ -0,0 +1,155 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function Canvas2D_Singleton() {
+ this._canvas = null;
+ this._canvasContext = null;
+ this._pixeldrawingCanvas = null;
+ this._canvasOffset = powerupjs.Vector2.zero;
+ }
+
+ Object.defineProperty(Canvas2D_Singleton.prototype, "offset",
+ {
+ get: function () {
+ return this._canvasOffset;
+ }
+ });
+
+ Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
+ {
+ get: function () {
+ return new powerupjs.Vector2(this._canvas.width / powerupjs.Game.size.x,
+ this._canvas.height / powerupjs.Game.size.y);
+ }
+ });
+
+ Canvas2D_Singleton.prototype.initialize = function (divName, canvasName) {
+ this._canvas = document.getElementById(canvasName);
+ this._div = document.getElementById(divName);
+
+ if (this._canvas.getContext)
+ this._canvasContext = this._canvas.getContext('2d');
+ else {
+ alert('Your browser is not HTML5 compatible.!');
+ return;
+ }
+
+ this._pixeldrawingCanvas = document.createElement('canvas');
+
+ window.onresize = this.resize;
+ this.resize();
+ };
+
+ Canvas2D_Singleton.prototype.clear = function () {
+ this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);
+ };
+
+ Canvas2D_Singleton.prototype.resize = function () {
+ var gameCanvas = powerupjs.Canvas2D._canvas;
+ var gameArea = powerupjs.Canvas2D._div;
+ var widthToHeight = powerupjs.Game.size.x / powerupjs.Game.size.y;
+ var newWidth = window.innerWidth;
+ var newHeight = window.innerHeight;
+ var newWidthToHeight = newWidth / newHeight;
+
+ if (newWidthToHeight > widthToHeight) {
+ newWidth = newHeight * widthToHeight;
+ } else {
+ newHeight = newWidth / widthToHeight;
+ }
+ gameArea.style.width = newWidth + 'px';
+ gameArea.style.height = newHeight + 'px';
+
+ gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
+ gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
+ gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';
+
+ gameCanvas.width = newWidth;
+ gameCanvas.height = newHeight;
+
+ var offset = powerupjs.Vector2.zero;
+ if (gameCanvas.offsetParent) {
+ do {
+ offset.x += gameCanvas.offsetLeft;
+ offset.y += gameCanvas.offsetTop;
+ } while ((gameCanvas = gameCanvas.offsetParent));
+ }
+ powerupjs.Canvas2D._canvasOffset = offset;
+ };
+
+ Canvas2D_Singleton.prototype.drawImage = function (sprite, position, rotation, scale, origin, sourceRect, mirror) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : powerupjs.Vector2.zero;
+ rotation = typeof rotation !== 'undefined' ? rotation : 0;
+ scale = typeof scale !== 'undefined' ? scale : 1;
+ origin = typeof origin !== 'undefined' ? origin : powerupjs.Vector2.zero;
+ sourceRect = typeof sourceRect !== 'undefined' ? sourceRect : new powerupjs.Rectangle(0, 0, sprite.width, sprite.height);
+
+ this._canvasContext.save();
+ if (mirror) {
+ this._canvasContext.scale(scale * canvasScale.x * -1, scale * canvasScale.y);
+ this._canvasContext.translate(-position.x - sourceRect.width, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ sourceRect.width - origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ else {
+ this._canvasContext.scale(scale * canvasScale.x, scale * canvasScale.y);
+ this._canvasContext.translate(position.x, position.y);
+ this._canvasContext.rotate(rotation);
+ this._canvasContext.drawImage(sprite, sourceRect.x, sourceRect.y,
+ sourceRect.width, sourceRect.height,
+ -origin.x, -origin.y,
+ sourceRect.width, sourceRect.height);
+ }
+ this._canvasContext.restore();
+ };
+
+ Canvas2D_Singleton.prototype.drawText = function (text, position, origin, color, textAlign, fontname, fontsize) {
+ var canvasScale = this.scale;
+
+ position = typeof position !== 'undefined' ? position : powerupjs.Vector2.zero;
+ origin = typeof origin !== 'undefined' ? origin : powerupjs.Vector2.zero;
+ color = typeof color !== 'undefined' ? color : powerupjs.Color.black;
+ textAlign = typeof textAlign !== 'undefined' ? textAlign : "top";
+ fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.translate(position.x - origin.x, position.y - origin.y);
+ this._canvasContext.textBaseline = 'top';
+ this._canvasContext.font = fontsize + " " + fontname;
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.textAlign = textAlign;
+ this._canvasContext.fillText(text, 0, 0);
+ this._canvasContext.restore();
+ };
+
+ Canvas2D_Singleton.prototype.drawPixel = function (x, y, color) {
+ var canvasscale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasscale.x, canvasscale.y);
+ this._canvasContext.fillStyle = color.toString();
+ this._canvasContext.fillRect(x, y, 1, 1);
+ this._canvasContext.restore();
+ };
+
+ Canvas2D_Singleton.prototype.drawRectangle = function (x, y, width, height) {
+ var canvasScale = this.scale;
+ this._canvasContext.save();
+ this._canvasContext.scale(canvasScale.x, canvasScale.y);
+ this._canvasContext.strokeRect(x, y, width, height);
+ this._canvasContext.restore();
+ };
+
+ powerupjs.Canvas2D = new Canvas2D_Singleton();
+
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/Game.js b/powerupjs/Game.js
new file mode 100644
index 0000000..f0a727c
--- /dev/null
+++ b/powerupjs/Game.js
@@ -0,0 +1,90 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ var requestAnimationFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+ })();
+
+ function Game_Singleton() {
+ this._totalTime = 0;
+ this._size = null;
+ this._spritesStillLoading = 0;
+ this._totalSprites = 0;
+ }
+
+ Object.defineProperty(Game_Singleton.prototype, "totalTime",
+ {
+ get: function () {
+ return this._totalTime;
+ }
+ });
+
+ Object.defineProperty(Game_Singleton.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+ Object.defineProperty(Game_Singleton.prototype, "screenRect",
+ {
+ get: function () {
+ return new powerupjs.Rectangle(0, 0, this._size.x, this._size.y);
+ }
+ });
+
+ Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
+ this._size = new powerupjs.Vector2(x, y);
+
+ powerupjs.Canvas2D.initialize(divName, canvasName);
+ this.loadAssets();
+ this.assetLoadingLoop();
+ };
+
+ Game_Singleton.prototype.initialize = function () {
+ };
+
+ Game_Singleton.prototype.loadAssets = function () {
+ };
+
+ Game_Singleton.prototype.assetLoadingLoop = function () {
+ powerupjs.Canvas2D.clear();
+ powerupjs.Canvas2D.drawText(Math.round((powerupjs.Game._totalSprites - powerupjs.Game._spritesStillLoading) /
+ powerupjs.Game._totalSprites * 100) + "%");
+
+ if (powerupjs.Game._spritesStillLoading > 0)
+ requestAnimationFrame(powerupjs.Game.assetLoadingLoop);
+ else {
+ powerupjs.Game.initialize();
+ requestAnimationFrame(powerupjs.Game.mainLoop);
+ }
+ };
+
+ Game_Singleton.prototype.mainLoop = function () {
+ var delta = 1 / 60;
+ powerupjs.Game._totalTime += delta;
+
+ powerupjs.GameStateManager.handleInput(delta);
+ powerupjs.GameStateManager.update(delta);
+ powerupjs.Canvas2D.clear();
+ powerupjs.GameStateManager.draw();
+
+ powerupjs.Keyboard.reset();
+ powerupjs.Mouse.reset();
+ powerupjs.Touch.reset();
+
+ requestAnimationFrame(powerupjs.Game.mainLoop);
+ };
+
+ powerupjs.Game = new Game_Singleton();
+ return powerupjs;
+
+}(powerupjs || {}));
diff --git a/powerupjs/GameStateManager.js b/powerupjs/GameStateManager.js
new file mode 100644
index 0000000..25cccd1
--- /dev/null
+++ b/powerupjs/GameStateManager.js
@@ -0,0 +1,53 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function GameStateManager_Singleton() {
+ this._gameStates = [];
+ this._currentGameState = null;
+ }
+
+ GameStateManager_Singleton.prototype.add = function (gamestate) {
+ this._gameStates.push(gamestate);
+ this._currentGameState = gamestate;
+ return this._gameStates.length - 1;
+ };
+
+ GameStateManager_Singleton.prototype.get = function (id) {
+ if (id < 0 || id >= this._gameStates.length)
+ return null;
+ else
+ return this._gameStates[id];
+ };
+
+ GameStateManager_Singleton.prototype.switchTo = function (id) {
+ if (id < 0 || id >= this._gameStates.length)
+ return;
+ this._currentGameState = this._gameStates[id];
+ };
+
+ GameStateManager_Singleton.prototype.handleInput = function (delta) {
+ if (this._currentGameState != null)
+ this._currentGameState.handleInput(delta);
+ };
+
+ GameStateManager_Singleton.prototype.update = function (delta) {
+ if (this._currentGameState != null)
+ this._currentGameState.update(delta);
+ };
+
+ GameStateManager_Singleton.prototype.draw = function () {
+ if (this._currentGameState != null)
+ this._currentGameState.draw();
+ };
+
+ GameStateManager_Singleton.prototype.reset = function () {
+ if (this._currentGameState != null)
+ this._currentGameState.reset();
+ };
+
+ powerupjs.GameStateManager = new GameStateManager_Singleton();
+ return powerupjs;
+
+})(powerupjs || {});
+
diff --git a/powerupjs/IGameLoopObject.js b/powerupjs/IGameLoopObject.js
new file mode 100644
index 0000000..513d37c
--- /dev/null
+++ b/powerupjs/IGameLoopObject.js
@@ -0,0 +1,26 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function IGameLoopObject() {
+ }
+
+ IGameLoopObject.prototype.initialize = function () {
+ };
+
+ IGameLoopObject.prototype.handleInput = function (delta) {
+ };
+
+ IGameLoopObject.prototype.update = function (delta) {
+ };
+
+ IGameLoopObject.prototype.draw = function () {
+ };
+
+ IGameLoopObject.prototype.reset = function () {
+ };
+
+ powerupjs.IGameLoopObject = IGameLoopObject;
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file
diff --git a/powerupjs/LAB.min.js b/powerupjs/LAB.min.js
new file mode 100644
index 0000000..e710dfe
--- /dev/null
+++ b/powerupjs/LAB.min.js
@@ -0,0 +1,5 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this);
\ No newline at end of file
diff --git a/powerupjs/Math.js b/powerupjs/Math.js
new file mode 100644
index 0000000..5e1da3b
--- /dev/null
+++ b/powerupjs/Math.js
@@ -0,0 +1,23 @@
+"use strict";
+
+if (!Math.sign) {
+ Math.sign = function (value) {
+ if (value > 0)
+ return 1;
+ else if (value < 0)
+ return -1;
+ else
+ return 0;
+ };
+}
+
+if (!Math.clamp) {
+ Math.clamp = function (value, min, max) {
+ if (value < min)
+ return min;
+ else if (value > max)
+ return max;
+ else
+ return value;
+ };
+}
\ No newline at end of file
diff --git a/powerupjs/Sound.js b/powerupjs/Sound.js
new file mode 100644
index 0000000..9edfebe
--- /dev/null
+++ b/powerupjs/Sound.js
@@ -0,0 +1,42 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function Sound(sound, looping) {
+ this.looping = typeof looping !== 'undefined' ? looping : false;
+ this.snd = new Audio();
+ if (this.snd.canPlayType("audio/ogg")) {
+ this.snd.src = sound + ".ogg";
+ } else if (this.snd.canPlayType("audio/mpeg")) {
+ this.snd.src = sound + ".mp3";
+ } else // we cannot play audio in this browser
+ this.snd = null;
+ }
+
+ Object.defineProperty(Sound.prototype, "volume",
+ {
+ get: function () {
+ return this.snd.volume;
+ },
+ set: function (value) {
+ this.snd.volume = value;
+ }
+ });
+
+ Sound.prototype.play = function () {
+ if (this.snd === null)
+ return;
+ this.snd.load();
+ this.snd.autoplay = true;
+ if (!this.looping)
+ return;
+ this.snd.addEventListener('ended', function () {
+ this.load();
+ this.autoplay = true;
+ }, false);
+ };
+
+ powerupjs.Sound = Sound;
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file
diff --git a/powerupjs/SpriteSheet.js b/powerupjs/SpriteSheet.js
new file mode 100644
index 0000000..5b774e4
--- /dev/null
+++ b/powerupjs/SpriteSheet.js
@@ -0,0 +1,125 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function SpriteSheet(imageName, createCollisionMask) {
+ console.log("Loading sprite: " + imageName);
+ powerupjs.Game._spritesStillLoading += 1;
+ powerupjs.Game._totalSprites += 1;
+
+ this._image = new Image();
+ this._image.src = imageName;
+ this._sheetColumns = 1;
+ this._sheetRows = 1;
+ this._collisionMask = null;
+
+ var sprite = this;
+ this._image.onload = function () {
+ if (createCollisionMask) {
+ console.log("Creating collision mask for sprite: " + imageName);
+ sprite.createPixelCollisionMask();
+ }
+ powerupjs.Game._spritesStillLoading -= 1;
+ };
+
+ // determine the number of sheet rows and columns
+ var pathSplit = imageName.split('/');
+ var fileName = pathSplit[pathSplit.length - 1];
+ var fileSplit = fileName.split("/")[0].split(".")[0].split("@");
+ if (fileSplit.length <= 1)
+ return;
+ var colRow = fileSplit[fileSplit.length - 1].split("x");
+ this._sheetColumns = colRow[0];
+ if (colRow.length === 2)
+ this._sheetRows = colRow[1];
+ }
+
+ Object.defineProperty(SpriteSheet.prototype, "image",
+ {
+ get: function () {
+ return this._image;
+ }
+ });
+
+ Object.defineProperty(SpriteSheet.prototype, "width",
+ {
+ get: function () {
+ return this._image.width / this._sheetColumns;
+ }
+ });
+
+ Object.defineProperty(SpriteSheet.prototype, "height",
+ {
+ get: function () {
+ return this._image.height / this._sheetRows;
+ }
+ });
+
+ Object.defineProperty(SpriteSheet.prototype, "size",
+ {
+ get: function () {
+ return new powerupjs.Vector2(this.width, this.height);
+ }
+ });
+
+ Object.defineProperty(SpriteSheet.prototype, "center",
+ {
+ get: function () {
+ return this.size.divideBy(2);
+ }
+ });
+
+ Object.defineProperty(SpriteSheet.prototype, "nrSheetElements",
+ {
+ get: function () {
+ return this._sheetRows * this._sheetColumns;
+ }
+ });
+
+ SpriteSheet.prototype.createPixelCollisionMask = function () {
+ this._collisionMask = [];
+ var w = this._image.width;
+ var h = this._image.height;
+ powerupjs.Canvas2D._pixeldrawingCanvas.width = w;
+ powerupjs.Canvas2D._pixeldrawingCanvas.height = h;
+ var ctx = powerupjs.Canvas2D._pixeldrawingCanvas.getContext('2d');
+
+ ctx.clearRect(0, 0, w, h);
+ ctx.save();
+ ctx.drawImage(this._image, 0, 0, w, h, 0, 0, w, h);
+ ctx.restore();
+ var imageData = ctx.getImageData(0, 0, w, h);
+ for (var x = 3, l = w * h * 4; x < l; x += 4) {
+ this._collisionMask.push(imageData.data[x]);
+ }
+ };
+
+ SpriteSheet.prototype.getAlpha = function (x, y, sheetIndex, mirror) {
+ if (this._collisionMask === null)
+ return 255;
+
+ var columnIndex = sheetIndex % this._sheetColumns;
+ var rowIndex = Math.floor(sheetIndex / this._sheetColumns) % this._sheetRows;
+ var textureX = columnIndex * this.width + x;
+ if (mirror)
+ textureX = (columnIndex + 1) * this.width - x - 1;
+ var textureY = rowIndex * this.height + y;
+ var arrayIndex = Math.floor(textureY * this._image.width + textureX);
+ if (arrayIndex < 0 || arrayIndex >= this._collisionMask.length)
+ return 0;
+ else
+ return this._collisionMask[arrayIndex];
+ };
+
+ SpriteSheet.prototype.draw = function (position, origin, sheetIndex, mirror) {
+ var columnIndex = sheetIndex % this._sheetColumns;
+ var rowIndex = Math.floor(sheetIndex / this._sheetColumns) % this._sheetRows;
+ var imagePart = new powerupjs.Rectangle(columnIndex * this.width, rowIndex * this.height,
+ this.width, this.height);
+ powerupjs.Canvas2D.drawImage(this._image, position, 0, 1, origin, imagePart, mirror);
+ };
+
+ powerupjs.SpriteSheet = SpriteSheet;
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/gameobjects/AnimatedGameObject.js b/powerupjs/gameobjects/AnimatedGameObject.js
new file mode 100644
index 0000000..7f6badf
--- /dev/null
+++ b/powerupjs/gameobjects/AnimatedGameObject.js
@@ -0,0 +1,48 @@
+"use strict";
+var powerupjs = (function (powerupjs) {
+
+ function AnimatedGameObject(layer, id) {
+ powerupjs.SpriteGameObject.call(this, null, layer, id);
+
+ this._animations = {};
+ this._current = null;
+ this._time = 0;
+ }
+
+ AnimatedGameObject.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+ AnimatedGameObject.prototype.loadAnimation = function (animname, id, looping, frametime) {
+ this._animations[id] = new powerupjs.Animation(animname, looping, frametime);
+ };
+
+ AnimatedGameObject.prototype.playAnimation = function (id) {
+ if (this._current === this._animations[id])
+ return;
+ this._sheetIndex = 0;
+ this._time = 0;
+ this._current = this._animations[id];
+ this.sprite = this._current.sprite;
+ };
+
+ AnimatedGameObject.prototype.animationEnded = function () {
+ return !this._current.looping && this.sheetIndex >= this.sprite.nrSheetElements - 1;
+ };
+
+ AnimatedGameObject.prototype.update = function (delta) {
+ this._time += delta;
+ while (this._time > this._current.frameTime) {
+ this._time -= this._current.frameTime;
+ this._sheetIndex++;
+ if (this._sheetIndex > this.sprite.nrSheetElements - 1)
+ if (this._current.looping)
+ this._sheetIndex = 0;
+ else
+ this._sheetIndex = this.sprite.nrSheetElements - 1;
+ }
+ powerupjs.SpriteGameObject.prototype.update.call(this, delta);
+ };
+
+ powerupjs.AnimatedGameObject = AnimatedGameObject;
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/gameobjects/GameObject.js b/powerupjs/gameobjects/GameObject.js
new file mode 100644
index 0000000..fb46ca6
--- /dev/null
+++ b/powerupjs/gameobjects/GameObject.js
@@ -0,0 +1,63 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function GameObject(layer, id) {
+ powerupjs.IGameLoopObject.call(this);
+
+ this.layer = typeof layer !== 'undefined' ? layer : 0;
+ this.id = typeof id !== 'undefined' ? id : 0;
+ this.parent = null;
+ this.position = powerupjs.Vector2.zero;
+ this.velocity = powerupjs.Vector2.zero;
+ this._visible = true;
+ }
+
+ GameObject.prototype = Object.create(powerupjs.IGameLoopObject.prototype);
+
+ Object.defineProperty(GameObject.prototype, "visible",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this._visible;
+ else
+ return this._visible && this.parent.visible;
+ },
+
+ set: function (value) {
+ this._visible = value;
+ }
+ });
+
+ Object.defineProperty(GameObject.prototype, "root",
+ {
+ get: function () {
+ if (this.parent === null)
+ return this;
+ else
+ return this.parent.root;
+ }
+ });
+
+ Object.defineProperty(GameObject.prototype, "worldPosition",
+ {
+ get: function () {
+ if (this.parent !== null)
+ return this.parent.worldPosition.addTo(this.position);
+ else
+ return this.position.copy();
+ }
+ });
+
+ GameObject.prototype.reset = function () {
+ this._visible = true;
+ };
+
+ GameObject.prototype.update = function (delta) {
+ this.position.addTo(this.velocity.multiply(delta));
+ };
+
+ powerupjs.GameObject = GameObject;
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file
diff --git a/powerupjs/gameobjects/GameObjectGrid.js b/powerupjs/gameobjects/GameObjectGrid.js
new file mode 100644
index 0000000..96c9b54
--- /dev/null
+++ b/powerupjs/gameobjects/GameObjectGrid.js
@@ -0,0 +1,64 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function GameObjectGrid(rows, columns, layer, id) {
+ powerupjs.GameObjectList.call(this, layer, id);
+
+ this.cellWidth = 0;
+ this.cellHeight = 0;
+ this._rows = rows;
+ this._columns = columns;
+ }
+
+ GameObjectGrid.prototype = Object.create(powerupjs.GameObjectList.prototype);
+
+ Object.defineProperty(GameObjectGrid.prototype, "rows", {
+ get: function () {
+ return this._rows;
+ }
+ });
+
+ Object.defineProperty(GameObjectGrid.prototype, "columns", {
+ get: function () {
+ return this._columns;
+ }
+ });
+
+ GameObjectGrid.prototype.add = function (gameobject) {
+ var row = Math.floor(this._gameObjects.length / this._columns);
+ var col = this._gameObjects.length % this._columns;
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ gameobject.position = new powerupjs.Vector2(col * this.cellWidth, row * this.cellHeight);
+ };
+
+ GameObjectGrid.prototype.addAt = function (gameobject, col, row) {
+ this._gameObjects[row * this._columns + col] = gameobject;
+ gameobject.parent = this;
+ gameobject.position = new powerupjs.Vector2(col * this.cellWidth, row * this.cellHeight);
+ };
+
+ GameObjectGrid.prototype.at = function (col, row) {
+ var index = row * this._columns + col;
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ else
+ return this._gameObjects[index];
+ };
+
+ GameObjectGrid.prototype.getAnchorPosition = function (gameobject) {
+ var l = this._gameObjects.length;
+ for (var i = 0; i < l; ++i)
+ if (this._gameObjects[i] == gameobject) {
+ var row = Math.floor(i / this.columns);
+ var col = i - row * this.columns;
+ return new powerupjs.Vector2(col * this.cellWidth, row * this.cellHeight);
+ }
+ return powerupjs.Vector2.zero;
+ };
+
+ powerupjs.GameObjectGrid = GameObjectGrid;
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/gameobjects/GameObjectList.js b/powerupjs/gameobjects/GameObjectList.js
new file mode 100644
index 0000000..3ed1d07
--- /dev/null
+++ b/powerupjs/gameobjects/GameObjectList.js
@@ -0,0 +1,86 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ GameObjectList.prototype = Object.create(powerupjs.GameObject.prototype);
+
+ function GameObjectList(layer, id) {
+ powerupjs.GameObject.call(this, layer, id);
+
+ this._gameObjects = [];
+ }
+
+ Object.defineProperty(GameObjectList.prototype, "length", {
+ get: function () {
+ return this._gameObjects.length;
+ }
+ });
+
+ GameObjectList.prototype.add = function (gameobject) {
+ this._gameObjects.push(gameobject);
+ gameobject.parent = this;
+ this._gameObjects.sort(function (a, b) {
+ return a.layer - b.layer;
+ });
+ };
+
+ GameObjectList.prototype.remove = function (gameobject) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (gameobject !== this._gameObjects[i])
+ continue;
+ this._gameObjects.splice(i, 1);
+ gameobject.parent = null;
+ return;
+ }
+ };
+
+ GameObjectList.prototype.at = function (index) {
+ if (index < 0 || index >= this._gameObjects.length)
+ return null;
+ return this._gameObjects[index];
+ };
+
+ GameObjectList.prototype.clear = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].parent = null;
+ this._gameObjects = [];
+ };
+
+ GameObjectList.prototype.find = function (id) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i) {
+ if (this._gameObjects[i].id === id)
+ return this._gameObjects[i];
+ if (this._gameObjects[i] instanceof powerupjs.GameObjectList) {
+ var obj = this._gameObjects[i].find(id);
+ if (obj !== null)
+ return obj;
+ }
+ }
+ return null;
+ };
+
+ GameObjectList.prototype.handleInput = function (delta) {
+ for (var i = this._gameObjects.length - 1; i >= 0; --i)
+ this._gameObjects[i].handleInput(delta);
+ };
+
+ GameObjectList.prototype.update = function (delta) {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].update(delta);
+ };
+
+ GameObjectList.prototype.draw = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ if (this._gameObjects[i].visible)
+ this._gameObjects[i].draw();
+ };
+
+ GameObjectList.prototype.reset = function () {
+ for (var i = 0, l = this._gameObjects.length; i < l; ++i)
+ this._gameObjects[i].reset();
+ };
+
+ powerupjs.GameObjectList = GameObjectList;
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/gameobjects/SpriteGameObject.js b/powerupjs/gameobjects/SpriteGameObject.js
new file mode 100644
index 0000000..b780411
--- /dev/null
+++ b/powerupjs/gameobjects/SpriteGameObject.js
@@ -0,0 +1,112 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function SpriteGameObject(sprite, layer, id) {
+ powerupjs.GameObject.call(this, layer, id);
+
+ this.sprite = sprite;
+
+ this.origin = powerupjs.Vector2.zero;
+ this.mirror = false;
+ this._sheetIndex = 0;
+ }
+
+ SpriteGameObject.prototype = Object.create(powerupjs.GameObject.prototype);
+
+ Object.defineProperty(SpriteGameObject.prototype, "size",
+ {
+ get: function () {
+ return this.sprite.size;
+ }
+ });
+
+ Object.defineProperty(SpriteGameObject.prototype, "width",
+ {
+ get: function () {
+ return this.sprite.width;
+ }
+ });
+
+ Object.defineProperty(SpriteGameObject.prototype, "height",
+ {
+ get: function () {
+ return this.sprite.height;
+ }
+ });
+
+ Object.defineProperty(SpriteGameObject.prototype, "center",
+ {
+ get: function () {
+ return this.sprite.center;
+ }
+ });
+
+ Object.defineProperty(SpriteGameObject.prototype, "sheetIndex",
+ {
+ get: function () {
+ return this._sheetIndex;
+ },
+ set: function (value) {
+ if (value >= 0)
+ this._sheetIndex = value % this.sprite.nrSheetElements;
+ }
+ });
+
+ Object.defineProperty(SpriteGameObject.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (powerupjs.Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+ Object.defineProperty(SpriteGameObject.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (powerupjs.Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+ Object.defineProperty(SpriteGameObject.prototype, "screenCenter",
+ {
+ get: function () {
+ return powerupjs.Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+ Object.defineProperty(SpriteGameObject.prototype, "boundingBox",
+ {
+ get: function () {
+ var leftTop = this.worldPosition.subtractFrom((this.origin));
+ return new powerupjs.Rectangle(leftTop.x, leftTop.y, this.width, this.height);
+ }
+ });
+
+ SpriteGameObject.prototype.draw = function () {
+ if (this._visible)
+ this.sprite.draw(this.worldPosition, this.origin, this._sheetIndex, this.mirror);
+ };
+
+ SpriteGameObject.prototype.collidesWith = function (obj) {
+ if (!this.visible || !obj.visible || !this.boundingBox.intersects(obj.boundingBox))
+ return false;
+ var intersect = this.boundingBox.intersection(obj.boundingBox);
+ var local = intersect.position.subtractFrom(this.worldPosition.subtractFrom(this.origin));
+ var objLocal = intersect.position.subtractFrom(obj.worldPosition.subtractFrom(obj.origin));
+ for (var x = 0; x < intersect.width; x++)
+ for (var y = 0; y < intersect.height; y++) {
+ if (this.getAlpha(Math.floor(local.x + x), Math.floor(local.y + y)) !== 0
+ && obj.getAlpha(Math.floor(objLocal.x + x), Math.floor(objLocal.y + y)) !== 0)
+ return true;
+ }
+ return false;
+ };
+
+ SpriteGameObject.prototype.getAlpha = function (x, y) {
+ return this.sprite.getAlpha(x, y, this._sheetIndex, this.mirror);
+ };
+
+ powerupjs.SpriteGameObject = SpriteGameObject;
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/geom/Rectangle.js b/powerupjs/geom/Rectangle.js
new file mode 100644
index 0000000..c37965f
--- /dev/null
+++ b/powerupjs/geom/Rectangle.js
@@ -0,0 +1,102 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function Rectangle(x, y, w, h) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ this.width = typeof w !== 'undefined' ? w : 1;
+ this.height = typeof h !== 'undefined' ? h : 1;
+ }
+
+ Object.defineProperty(Rectangle.prototype, "left",
+ {
+ get: function () {
+ return this.x;
+ }
+ });
+
+ Object.defineProperty(Rectangle.prototype, "right",
+ {
+ get: function () {
+ return this.x + this.width;
+ }
+ });
+
+ Object.defineProperty(Rectangle.prototype, "top",
+ {
+ get: function () {
+ return this.y;
+ }
+ });
+
+ Object.defineProperty(Rectangle.prototype, "bottom",
+ {
+ get: function () {
+ return this.y + this.height;
+ }
+ });
+
+ Object.defineProperty(Rectangle.prototype, "center",
+ {
+ get: function () {
+ return this.position.addTo(this.size.divideBy(2));
+ }
+ });
+
+ Object.defineProperty(Rectangle.prototype, "position",
+ {
+ get: function () {
+ return new powerupjs.Vector2(this.x, this.y);
+ }
+ });
+
+ Object.defineProperty(Rectangle.prototype, "size",
+ {
+ get: function () {
+ return new powerupjs.Vector2(this.width, this.height);
+ }
+ });
+
+ Rectangle.prototype.contains = function (v) {
+ v = typeof v !== 'undefined' ? v : new powerupjs.Vector2();
+ return (v.x >= this.left && v.x <= this.right &&
+ v.y >= this.top && v.y <= this.bottom);
+ };
+
+ Rectangle.prototype.intersects = function (rect) {
+ return (this.left <= rect.right && this.right >= rect.left &&
+ this.top <= rect.bottom && this.bottom >= rect.top);
+ };
+
+ Rectangle.prototype.calculateIntersectionDepth = function (rect) {
+ var minDistance = this.size.addTo(rect.size).divideBy(2);
+ var distance = this.center.subtractFrom(rect.center);
+ var depth = powerupjs.Vector2.zero;
+ if (distance.x > 0)
+ depth.x = minDistance.x - distance.x;
+ else
+ depth.x = -minDistance.x - distance.x;
+ if (distance.y > 0)
+ depth.y = minDistance.y - distance.y;
+ else
+ depth.y = -minDistance.y - distance.y;
+ return depth;
+ };
+
+ Rectangle.prototype.intersection = function (rect) {
+ var xmin = Math.max(this.left, rect.left);
+ var xmax = Math.min(this.right, rect.right);
+ var ymin = Math.max(this.top, rect.top);
+ var ymax = Math.min(this.bottom, rect.bottom);
+ return new powerupjs.Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
+ };
+
+ Rectangle.prototype.draw = function () {
+ powerupjs.Canvas2D.drawRectangle(this.x, this.y, this.width, this.height);
+ };
+
+ powerupjs.Rectangle = Rectangle;
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file
diff --git a/powerupjs/geom/Vector2.js b/powerupjs/geom/Vector2.js
new file mode 100644
index 0000000..727a7ce
--- /dev/null
+++ b/powerupjs/geom/Vector2.js
@@ -0,0 +1,121 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function Vector2(x, y) {
+ this.x = typeof x !== 'undefined' ? x : 0;
+ this.y = typeof y !== 'undefined' ? y : 0;
+ }
+
+ Object.defineProperty(Vector2, "zero",
+ {
+ get: function () {
+ return new powerupjs.Vector2();
+ }
+ });
+
+ Object.defineProperty(Vector2.prototype, "isZero",
+ {
+ get: function () {
+ return this.x === 0 && this.y === 0;
+ }
+ });
+
+ Object.defineProperty(Vector2.prototype, "length",
+ {
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ });
+
+ Vector2.prototype.addTo = function (v) {
+ if (v.constructor == Vector2) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x += v;
+ this.y += v;
+ }
+ return this;
+ };
+
+ Vector2.prototype.add = function (v) {
+ var result = this.copy();
+ return result.addTo(v);
+ };
+
+ Vector2.prototype.subtractFrom = function (v) {
+ if (v.constructor == Vector2) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x -= v;
+ this.y -= v;
+ }
+ return this;
+ };
+
+ Vector2.prototype.subtract = function (v) {
+ var result = this.copy();
+ return result.subtractFrom(v);
+ };
+
+ Vector2.prototype.divideBy = function (v) {
+ if (v.constructor == Vector2) {
+ this.x /= v.x;
+ this.y /= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x /= v;
+ this.y /= v;
+ }
+ return this;
+ };
+
+ Vector2.prototype.divide = function (v) {
+ var result = this.copy();
+ return result.divideBy(v);
+ };
+
+ Vector2.prototype.multiplyWith = function (v) {
+ if (v.constructor == Vector2) {
+ this.x *= v.x;
+ this.y *= v.y;
+ }
+ else if (v.constructor == Number) {
+ this.x *= v;
+ this.y *= v;
+ }
+ return this;
+ };
+
+ Vector2.prototype.multiply = function (v) {
+ var result = this.copy();
+ return result.multiplyWith(v);
+ };
+
+ Vector2.prototype.toString = function () {
+ return "(" + this.x + ", " + this.y + ")";
+ };
+
+ Vector2.prototype.normalize = function () {
+ var length = this.length;
+ if (length === 0)
+ return;
+ this.divideBy(length);
+ };
+
+ Vector2.prototype.copy = function () {
+ return new powerupjs.Vector2(this.x, this.y);
+ };
+
+ Vector2.prototype.equals = function (obj) {
+ return this.x === obj.x && this.y === obj.y;
+ };
+
+ powerupjs.Vector2 = Vector2;
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file
diff --git a/powerupjs/gui/Button.js b/powerupjs/gui/Button.js
new file mode 100644
index 0000000..1c5725e
--- /dev/null
+++ b/powerupjs/gui/Button.js
@@ -0,0 +1,25 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function Button(sprite, layer, id) {
+ powerupjs.SpriteGameObject.call(this, sprite, layer, id);
+
+ this.pressed = false;
+ this.down = false;
+ }
+
+ Button.prototype = Object.create(powerupjs.SpriteGameObject.prototype);
+
+ Button.prototype.handleInput = function (delta) {
+ var boundingBox = this.boundingBox;
+ this.pressed = this.visible && (powerupjs.Touch.containsTouchPress(boundingBox) ||
+ powerupjs.Mouse.containsMousePress(boundingBox));
+ this.down = this.visible && (powerupjs.Touch.containsTouch(boundingBox) ||
+ powerupjs.Mouse.containsMouseDown(boundingBox));
+ };
+
+ powerupjs.Button = Button;
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/gui/Label.js b/powerupjs/gui/Label.js
new file mode 100644
index 0000000..1400dbe
--- /dev/null
+++ b/powerupjs/gui/Label.js
@@ -0,0 +1,99 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function calculateTextSize(fontname, fontsize, text) {
+ var div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = -1000;
+ div.style.top = -1000;
+ document.body.appendChild(div);
+ text = typeof text !== 'undefined' ? text : "M";
+ div.style.fontSize = "" + fontsize;
+ div.style.fontFamily = fontname;
+ div.innerHTML = text;
+ var size = new powerupjs.Vector2(div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+ return size;
+ }
+
+ function Label(fontname, fontsize, layer, id) {
+ powerupjs.GameObject.call(this, layer, id);
+
+ this.color = powerupjs.Color.black;
+ this.origin = powerupjs.Vector2.zero;
+ this._fontname = typeof fontname !== 'undefined' ? fontname : "Courier New";
+ this._fontsize = typeof fontsize !== 'undefined' ? fontsize : "20px";
+ this._contents = "";
+ this._align = "left";
+ this._size = powerupjs.Vector2.zero;
+ }
+
+ Label.prototype = Object.create(powerupjs.GameObject.prototype);
+
+ Object.defineProperty(Label.prototype, "size",
+ {
+ get: function () {
+ return this._size;
+ }
+ });
+
+ Object.defineProperty(Label.prototype, "width",
+ {
+ get: function () {
+ return this._size.x;
+ }
+ });
+
+ Object.defineProperty(Label.prototype, "height",
+ {
+ get: function () {
+ return this._size.y;
+ }
+ });
+
+ Object.defineProperty(Label.prototype, "screenCenterX",
+ {
+ get: function () {
+ return (powerupjs.Game.size.x - this.width) / 2 + this.origin.x;
+ }
+ });
+
+ Object.defineProperty(Label.prototype, "screenCenterY",
+ {
+ get: function () {
+ return (powerupjs.Game.size.y - this.height) / 2 + this.origin.y;
+ }
+ });
+
+ Object.defineProperty(Label.prototype, "screenCenter",
+ {
+ get: function () {
+ return powerupjs.Game.size.subtract(this.size).divideBy(2).addTo(this.origin);
+ }
+ });
+
+ Object.defineProperty(Label.prototype, "text",
+ {
+ get: function () {
+ return this._contents;
+ },
+
+ set: function (value) {
+ this._contents = value;
+ this._size = calculateTextSize(this._fontname, this._fontsize, value);
+ }
+ });
+
+ Label.prototype.draw = function () {
+ if (!this.visible)
+ return;
+ powerupjs.Canvas2D.drawText(this._contents, this.worldPosition,
+ this.origin, this.color, this._align,
+ this._fontname, this._fontsize);
+ };
+
+ powerupjs.Label = Label;
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/input/ButtonState.js b/powerupjs/input/ButtonState.js
new file mode 100644
index 0000000..ffa97da
--- /dev/null
+++ b/powerupjs/input/ButtonState.js
@@ -0,0 +1,12 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+ function ButtonState() {
+ this.down = false;
+ this.pressed = false;
+ }
+
+ powerupjs.ButtonState = ButtonState;
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file
diff --git a/powerupjs/input/Keyboard.js b/powerupjs/input/Keyboard.js
new file mode 100644
index 0000000..1fcbf4f
--- /dev/null
+++ b/powerupjs/input/Keyboard.js
@@ -0,0 +1,45 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function handleKeyDown(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ if (!powerupjs.Keyboard._keyStates[code].down)
+ powerupjs.Keyboard._keyStates[code].pressed = true;
+ powerupjs.Keyboard._keyStates[code].down = true;
+ }
+
+ function handleKeyUp(evt) {
+ var code = evt.keyCode;
+ if (code < 0 || code > 255)
+ return;
+ powerupjs.Keyboard._keyStates[code].down = false;
+ }
+
+ function Keyboard_Singleton() {
+ this._keyStates = [];
+ for (var i = 0; i < 256; ++i)
+ this._keyStates.push(new powerupjs.ButtonState());
+ document.onkeydown = handleKeyDown;
+ document.onkeyup = handleKeyUp;
+ }
+
+ Keyboard_Singleton.prototype.reset = function () {
+ for (var i = 0; i < 256; ++i)
+ this._keyStates[i].pressed = false;
+ };
+
+ Keyboard_Singleton.prototype.pressed = function (key) {
+ return this._keyStates[key].pressed;
+ };
+
+ Keyboard_Singleton.prototype.down = function (key) {
+ return this._keyStates[key].down;
+ };
+
+ powerupjs.Keyboard = new Keyboard_Singleton();
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/input/Mouse.js b/powerupjs/input/Mouse.js
new file mode 100644
index 0000000..26a83eb
--- /dev/null
+++ b/powerupjs/input/Mouse.js
@@ -0,0 +1,97 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function handleMouseMove(evt) {
+ var canvasScale = powerupjs.Canvas2D.scale;
+ var canvasOffset = powerupjs.Canvas2D.offset;
+ var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
+ var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
+ powerupjs.Mouse._position = new powerupjs.Vector2(mx, my);
+ }
+
+ function handleMouseDown(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1) {
+ if (!powerupjs.Mouse._left.down)
+ powerupjs.Mouse._left.pressed = true;
+ powerupjs.Mouse._left.down = true;
+ } else if (evt.which === 2) {
+ if (!powerupjs.Mouse._middle.down)
+ powerupjs.Mouse._middle.pressed = true;
+ powerupjs.Mouse._middle.down = true;
+ } else if (evt.which === 3) {
+ if (!powerupjs.Mouse._right.down)
+ powerupjs.Mouse._right.pressed = true;
+ powerupjs.Mouse._right.down = true;
+ }
+ }
+
+ function handleMouseUp(evt) {
+ handleMouseMove(evt);
+
+ if (evt.which === 1)
+ powerupjs.Mouse._left.down = false;
+ else if (evt.which === 2)
+ powerupjs.Mouse._middle.down = false;
+ else if (evt.which === 3)
+ powerupjs.Mouse._right.down = false;
+ }
+
+ function Mouse_Singleton() {
+ this._position = powerupjs.Vector2.zero;
+ this._left = new powerupjs.ButtonState();
+ this._middle = new powerupjs.ButtonState();
+ this._right = new powerupjs.ButtonState();
+ document.onmousemove = handleMouseMove;
+ document.onmousedown = handleMouseDown;
+ document.onmouseup = handleMouseUp;
+ }
+
+ Object.defineProperty(Mouse_Singleton.prototype, "left",
+ {
+ get: function () {
+ return this._left;
+ }
+ });
+
+ Object.defineProperty(Mouse_Singleton.prototype, "middle",
+ {
+ get: function () {
+ return this._middle;
+ }
+ });
+
+ Object.defineProperty(Mouse_Singleton.prototype, "right",
+ {
+ get: function () {
+ return this._right;
+ }
+ });
+
+ Object.defineProperty(Mouse_Singleton.prototype, "position",
+ {
+ get: function () {
+ return this._position;
+ }
+ });
+
+ Mouse_Singleton.prototype.reset = function () {
+ this._left.pressed = false;
+ this._middle.pressed = false;
+ this._right.pressed = false;
+ };
+
+ Mouse_Singleton.prototype.containsMouseDown = function (rect) {
+ return this._left.down && rect.contains(this._position);
+ };
+
+ Mouse_Singleton.prototype.containsMousePress = function (rect) {
+ return this._left.pressed && rect.contains(this._position);
+ };
+
+ powerupjs.Mouse = new Mouse_Singleton();
+ return powerupjs;
+
+})(powerupjs || {});
diff --git a/powerupjs/input/Touch.js b/powerupjs/input/Touch.js
new file mode 100644
index 0000000..9ed8c8c
--- /dev/null
+++ b/powerupjs/input/Touch.js
@@ -0,0 +1,113 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ function handleTouchStart(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ powerupjs.Touch._touches.push(touches[i]);
+ powerupjs.Touch._touchPresses.push(true);
+ }
+ }
+
+ function handleTouchMove(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; i++) {
+ var id = powerupjs.Touch.getTouchIndexFromId(touches[i].identifier);
+ powerupjs.Touch._touches.splice(id, 1, touches[i]);
+ }
+ }
+
+ function handleTouchEnd(evt) {
+ evt.preventDefault();
+ var touches = evt.changedTouches;
+ for (var i = 0; i < touches.length; ++i) {
+ var id = powerupjs.Touch.getTouchIndexFromId(touches[i].identifier);
+ powerupjs.Touch._touches.splice(id, 1);
+ powerupjs.Touch._touchPresses.splice(id, 1);
+ }
+ }
+
+ function Touch_Singleton() {
+ this._touches = [];
+ this._touchPresses = [];
+ document.addEventListener('touchstart', handleTouchStart, false);
+ document.addEventListener('touchend', handleTouchEnd, false);
+ document.addEventListener('touchcancel', handleTouchEnd, false);
+ document.addEventListener('touchleave', handleTouchEnd, false);
+ document.body.addEventListener('touchmove', handleTouchMove, false);
+ }
+
+ Object.defineProperty(Touch_Singleton.prototype, "nrTouches",
+ {
+ get: function () {
+ return this._touches.length;
+ }
+ });
+
+ Object.defineProperty(Touch_Singleton.prototype, "isTouching",
+ {
+ get: function () {
+ return this._touches.length !== 0;
+ }
+ });
+
+ Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
+ {
+ get: function () {
+ return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
+ }
+ });
+
+ Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (this._touches[i].identifier === id)
+ return i;
+ }
+ return -1;
+ };
+
+ Touch_Singleton.prototype.reset = function () {
+ for (var i = 0, l = this._touchPresses.length; i < l; ++i)
+ this._touchPresses[i] = false;
+ };
+
+ Touch_Singleton.prototype.getPosition = function (index) {
+ var canvasScale = powerupjs.Canvas2D.scale;
+ var canvasOffset = powerupjs.Canvas2D.offset;
+ var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
+ var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
+ return new powerupjs.Vector2(mx, my);
+ };
+
+ Touch_Singleton.prototype.getIndexInRect = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ var pos = this.getPosition(i);
+ if (rect.contains(pos))
+ return i;
+ }
+ return powerupjs.Vector2.zero;
+ };
+
+ Touch_Singleton.prototype.containsTouch = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)))
+ return true;
+ }
+ return false;
+ };
+
+ Touch_Singleton.prototype.containsTouchPress = function (rect) {
+ for (var i = 0, l = this._touches.length; i < l; ++i) {
+ if (rect.contains(this.getPosition(i)) && this._touchPresses[i])
+ return true;
+ }
+ return false;
+ };
+
+ powerupjs.Touch = new Touch_Singleton();
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file
diff --git a/powerupjs/system/Color.js b/powerupjs/system/Color.js
new file mode 100644
index 0000000..4bbfbfd
--- /dev/null
+++ b/powerupjs/system/Color.js
@@ -0,0 +1,151 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ var Color = {
+ aliceBlue: "#F0F8FF",
+ antiqueWhite: "#FAEBD7",
+ aqua: "#00FFFF",
+ aquamarine: "#7FFFD4",
+ azure: "#F0FFFF",
+ beige: "#F5F5DC",
+ bisque: "#FFE4C4",
+ black: "#000000",
+ blanchedAlmond: "#FFEBCD",
+ blue: "#0000FF",
+ blueViolet: "#8A2BE2",
+ brown: "#A52A2A",
+ burlyWood: "#DEB887",
+ cadetBlue: "#5F9EA0",
+ chartreuse: "#7FFF00",
+ chocolate: "#D2691E",
+ coral: "#FF7F50",
+ cornflowerBlue: "#6495ED",
+ cornsilk: "#FFF8DC",
+ crimson: "#DC143C",
+ cyan: "#00FFFF",
+ darkBlue: "#00008B",
+ darkCyan: "#008B8B",
+ darkGoldenrod: "#B8860B",
+ darkGray: "#A9A9A9",
+ darkGreen: "#006400",
+ darkKhaki: "#BDB76B",
+ darkMagenta: "#8B008B",
+ darkOliveGreen: "#556B2F",
+ darkOrange: "#FF8C00",
+ darkOrchid: "#9932CC",
+ darkRed: "#8B0000",
+ darkSalmon: "#E9967A",
+ darkSeaGreen: "#8FBC8B",
+ darkSlateBlue: "#483D8B",
+ darkSlateGray: "#2F4F4F",
+ darkTurquoise: "#00CED1",
+ darkViolet: "#9400D3",
+ deepPink: "#FF1493",
+ deepSkyBlue: "#00BFFF",
+ dimGray: "#696969",
+ dodgerBlue: "#1E90FF",
+ firebrick: "#B22222",
+ floralWhite: "#FFFAF0",
+ forestGreen: "#228B22",
+ fuchsia: "#FF00FF",
+ gainsboro: "#DCDCDC",
+ ghostWhite: "#F8F8FF",
+ gold: "#FFD700",
+ goldenrod: "#DAA520",
+ gray: "#808080",
+ green: "#008000",
+ greenYellow: "#ADFF2F",
+ honeydew: "#F0FFF0",
+ hotPink: "#FF69B4",
+ indianRed: "#CD5C5C",
+ indigo: "#4B0082",
+ ivory: "#FFFFF0",
+ khaki: "#F0E68C",
+ lavender: "#E6E6FA",
+ lavenderBlush: "#FFF0F5",
+ lawnGreen: "#7CFC00",
+ lemonChiffon: "#FFFACD",
+ lightBlue: "#ADD8E6",
+ lightCoral: "#F080FF",
+ lightCyan: "#E0FFFF",
+ lightGoldenrodYellow: "#FAFAD2",
+ lightGray: "#D3D3D3",
+ lightGreen: "#90EE90",
+ lightPink: "#FFB6C1",
+ lightSalmon: "#FFA07A",
+ lightSeaGreen: "#20B2AA",
+ lightSkyBlue: "#87CEFA",
+ lightSlateGray: "#778899",
+ lightSteelBlue: "#B0C4DE",
+ lightYellow: "#FFFFE0",
+ lime: "#00FF00",
+ limeGreen: "#32CD32",
+ linen: "#FAF0E6",
+ magenta: "#FF00FF",
+ maroon: "#800000",
+ mediumAquamarine: "#66CDAA",
+ mediumBlue: "#0000CD",
+ mediumOrchid: "#BA55D3",
+ mediumPurple: "#9370DB",
+ mediumSeaGreen: "#3CB371",
+ mediumSlateBlue: "#7B68EE",
+ mediumSpringGreen: "#00FA9A",
+ mediumTurquoise: "#48D1CC",
+ mediumVioletRed: "#C71585",
+ midnightBlue: "#191970",
+ mintCream: "#F5FFFA",
+ mistyRose: "#FFE4E1",
+ moccasin: "#FFE4B5",
+ navajoWhite: "#FFDEAD",
+ navy: "#000080",
+ oldLace: "#FDF5E6",
+ olive: "#808000",
+ oliveDrab: "#6B8E23",
+ orange: "#FFA500",
+ orangeRed: "#FF4500",
+ orchid: "#DA70D6",
+ paleGoldenrod: "#EEE8AA",
+ paleGreen: "#98FB98",
+ paleTurquoise: "#AFEEEE",
+ paleVioletRed: "#DB7093",
+ papayaWhip: "#FFEFD5",
+ peachPuff: "#FFDAB9",
+ peru: "#CD853F",
+ pink: "#FFC0CB",
+ plum: "#DDA0DD",
+ powderBlue: "#B0E0E6",
+ purple: "#800080",
+ red: "#FF0000",
+ rosyBrown: "#BC8F8F",
+ royalBlue: "#4169E1",
+ saddleBrown: "#8B4513",
+ salmon: "#FA8072",
+ sandyBrown: "#F4A460",
+ seaGreen: "#2E8B57",
+ seaShell: "#FFF5EE",
+ sienna: "#A0522D",
+ silver: "#C0C0C0",
+ skyBlue: "#87CEEB",
+ slateBlue: "#6A5ACD",
+ slateGray: "#708090",
+ snow: "#FFFAFA",
+ springGreen: "#00FF7F",
+ steelBlue: "#4682B4",
+ tan: "#D2B48C",
+ teal: "#008080",
+ thistle: "#D8BFD8",
+ tomato: "#FF6347",
+ turquoise: "#40E0D0",
+ violet: "#EE82EE",
+ wheat: "#F5DEB3",
+ white: "#FFFFFF",
+ whiteSmoke: "#F5F5F5",
+ yellow: "#FFFF00",
+ yellowGreen: "#9ACD32"
+ };
+
+ powerupjs.Color = Color;
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file
diff --git a/powerupjs/system/Keys.js b/powerupjs/system/Keys.js
new file mode 100644
index 0000000..488d7c0
--- /dev/null
+++ b/powerupjs/system/Keys.js
@@ -0,0 +1,54 @@
+"use strict";
+
+var powerupjs = (function (powerupjs) {
+
+ var Keys = {
+ none: 0,
+ back: 8,
+ tab: 9,
+ enter: 13,
+ pause: 19,
+ escape: 27,
+
+ space: 32,
+
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40,
+
+ insert: 45,
+ del: 46,
+
+ d0: 48,
+ d1: 49,
+ d2: 50,
+ d3: 51,
+ d4: 52,
+ d5: 53,
+ d6: 54,
+ d7: 55,
+ d8: 56,
+ d9: 57,
+
+ A: 65, B: 66, C: 67, D: 68, E: 69, F: 70,
+ G: 71, H: 72, I: 73, J: 74, K: 75, L: 76,
+ M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82,
+ S: 83, T: 84, U: 85, V: 86, W: 87, X: 88,
+ Y: 89, Z: 90,
+
+ multiply: 42,
+ add: 43,
+ subtract: 45,
+ decimal: 46,
+ divide: 47
+ };
+
+ powerupjs.Keys = Keys;
+ return powerupjs;
+
+})(powerupjs || {});
\ No newline at end of file