commit f8fc83424e8d854b3b018c0d8961ec950b3df7df Author: Benjamin Kraft Date: Sun Mar 12 13:49:52 2023 +0100 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a25729d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +.env +node_modules diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..923b88d --- /dev/null +++ b/public/index.html @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + Bricks v3.1 JS + + +
+ +
+ Level: + + + +
+
+
+ + + + + + + + + +
Gesamtspielzeit:
Gesamtanzahl Blöcke:
+ + + + + + +
BlöckeLevelRekordzeit
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Start / Stop:SPACE
Bewegen:← →
Neustart:SHIFT + R
Daten löschen:SHIFT + D
Interface Aus/An:ESC / TAB
+ +
+
+
+
+ +
+
+ +
+
+
+

Anleitung Bricks v3.1

+
+

Über das Spiel:

+

+ Die Aufgabe in dem Spiel besteht darin, jeden farbigen Block auf dem Spielfeld + mithilfe des Paddleboards zu zerstören! + Spezielle Items können helfen...oder nicht :D +

+

+ Alle deine Statistiken und Fortschritte werden in Cookies gespeichert, + also stelle sicher, dass dein Browser Cookies zulässt und diese nicht + automatisch löscht. +

+

+ Ein Level ist gewonnen wenn alle farbigen Blöcke zerstört wurden + und es ist verloren wenn alle Bälle aus dem Spielfeld verschwinden. Aber keine Sorge, + du hast eine unendliche Anzahl an Zügen :)
+ Jedes mal, wenn du ein Level gewinnst, bist du in der Lage das Nächsthöhere zu starten. +

+

Gameplay → Blöcke:

+

+ Bis jetzt gibt es 9 verschiedene Arten von Blöcken: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Dunkelgrün
2Hellgrün
3Hellblau
4Dunkelblau
5Violett
6Rot
7Orange
8Gelb
(9)Grau
+

+ Jeder Treffer auf einen Block verringert dessen Status um 1, + bis dieser 0 ist, denn dann ist der Block "tot" und verschwindet. +
+ Die grauen Blöcke mit dem Wert 9 bzw. -1 sind eine Ausnahme, da sie unzerstörbar sind. +

+

Gameplay → Items:

+

+ Ab und zu entsteht ein Item, nachdem ein Block zerstört wurde, um + es aufzusammeln, benutzt du ganz einfach dein Paddleboard - + Deine Bälle können sie nicht aufsammeln. + Je höher dein Level, desto häufiger entstehen Items, du wirst sie + anfangs also nur sehr selten antreffen. +

+

+ Hier sind alle Items gelistet: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Ball→ Boost
Paddle→ Boost
Ball→ Slow
Paddle→ Slow
Ball→ Creator
Brick→ Repair
+

+ Ein Boost gibt dem Objekt einen permanenten Geschwindigkeitsschub, + während ein Slow das Gegenteil verursacht.
+ Der Ball-Creator fügt dem Spiel einen neuen Ball hinzu. +

+

+ Pass auf bei dem Repair-Item, + denn das macht jeden Block im aktuellen Spiel + eine Stufe stärker. +

+

Weitere Informationen:

+

+ Dieses Spiel wurde nicht fürs Handy programmiert, dort sind viele Bugs anzutreffen. + Alle nötigen Hotkeys sind links in der Info-Box aufgelistet. +

+

+ Ich benutze für dieses Spiel JavaScript in Kombination mit der Processing + Library p5 von Lauren McCarthy und der sehr nützlichen auf HTML-DOM arbeitenden Library jQuery. +

+

Changelog:

+

+ Version 3.0:
+ Erste eigene Web-Version von "Bricks" +

+

+ Version 3.1:
+ Die ersten 5 Level hinzugefügt. Viel Spaß! +

+

+ Erstellt und veröffentlicht von BenjoCraeft,
+ Gamer, Programmierer +

+

+ Schließe diese Box mit ESC +

+
+
+
+ + \ No newline at end of file diff --git a/public/pictures/favicon.ico b/public/pictures/favicon.ico new file mode 100644 index 0000000..e5e6112 Binary files /dev/null and b/public/pictures/favicon.ico differ diff --git a/public/scripts/Level.js b/public/scripts/Level.js new file mode 100644 index 0000000..90fd1ef --- /dev/null +++ b/public/scripts/Level.js @@ -0,0 +1,379 @@ +var strokePadding = 3; + +function Level(level){ + + this.startingPoint = Math.random(); + + var table = tableSwitch(level, this); + + this.cols = table.cols; + this.rows = table.rows; + + this.setDefault = function(){ + + this.isStarted = false; + this.isPaused = false; + this.isWon = false; + this.isLost = false; + + this.levelNum = level; + this.recordTime = getRecordTime(level); + this.totalBricksDestroyed = getTotalBricksDestroyed(level); + this.currentTime = 0; + + this.brickPadding = (wWidth * 0.0125 + wHeight * 0.0125) / 2; + this.brickWidth = (wWidth - this.brickPadding * (this.cols + 1)) / this.cols; + this.brickHeight = (wHeight * 0.75 - this.brickPadding * (this.rows + 1)) / this.rows; + + this.bricks = []; + this.balls = []; + this.toBeErased = []; + this.items = []; + + this.itemFrequency = floor(levelCount / this.levelNum) * 2; + + var p = new Paddle(); + p.v = 10; + p.width = wWidth * 0.15; + p.x = (wWidth - p.width) / 2; + p.y = wHeight * 0.95; + p.height = wHeight * 0.025; + + var b = new Ball(); + b.radius = (wWidth * 0.02 + wHeight * 0.02) / 2; + b.x = p.x + this.startingPoint * p.width; + b.y = p.y - b.radius - strokePadding; + var velocityVector = {mag: (wWidth * 0.01 + wHeight * 0.01) / 3, x: 0, y: 0}; + b.v = velocityVector; + b.v.x = b.calcVelocityX(p, 0); + b.v.y = -b.calcVelocityY(); + + this.paddle = p; + this.balls.push(b); + + for (var c = 1; c <= this.cols; c++){ + for (var r = 1; r <= this.rows; r++){ + var b = new Brick(); + b.x = this.brickPadding + (c - 1) * (this.brickWidth + this.brickPadding); + b.y = this.brickPadding + (r - 1) * (this.brickHeight + this.brickPadding); + b.width = this.brickWidth; + b.height = this.brickHeight; + b.state = designSwitch(level, c, r); + this.bricks.push(b); + } + } + + //Border Top + bt = new Frameborder(0, -50, wWidth, 50); + //Border Bottom + bb = new Frameborder(0, wHeight, wWidth, 50); + //Border Left + bl = new Frameborder(-50, 0, 50, wHeight); + //Border Right + br = new Frameborder(wWidth, 0, 50, wHeight); + + this.frameBorders = [bt, bb, bl, br]; + } + + this.setDefault(); + + //Draw everything and check and react to collisions and events + this.drawShapes = function(){ + + if (this.isWon || this.isLost) return; + + //Background gets cleared and bg color is gray + clear(); + background(100); + + //Bricks have black stroke and rounded corners + stroke(0); + strokeWeight(strokePadding * 2); + strokeJoin(ROUND); + + //Counter to check if game is won + var brickCounter = 0; + + //Draw bricks + for (var b of this.bricks){ + + //If there are bricks "living" counter goes up and game keeps running + if (b.state > 0) brickCounter++; + + //Select brick color based on his state + var fillColor = colorSwitch(b.state); + if (!fillColor) continue; + else fill(fillColor); + + //Drawing + rect(b.x, b.y, b.width, b.height); + } + + //If no brick is "living" game is won + if (brickCounter == 0) this.gameWon(); //Deactivate in TestMode + + //Draw downfalling items + for (var i of this.items){ + var sw = i.radius * 0.2; + + fill(0); + stroke(0); + strokeWeight(0); + ellipse(i.x, i.y, i.radius * 2, i.radius * 2); + strokeWeight(sw); + + var rectX = i.x - i.radius * sin(5 * PI / 8) + sw; + var rectY = i.y + i.radius * cos(5 * PI / 8) + sw; + var rectW = i.radius * 2 * sin(5 * PI / 8) - sw * 2; + var rectH = i.radius * 2 * abs(cos(5 * PI / 8)) - sw * 2; + + var lineLength = i.radius * 0.5; + + switch(i.item){ + case FastBall: + fill("#00FF00"); + strokeWeight(0); + ellipse(i.x, i.y, i.radius * 1.25, i.radius * 1.25); + break; + case FastPaddle: + fill("#00FF00"); + strokeWeight(0); + rect(rectX, rectY, rectW, rectH); + break; + case SlowBall: + fill("#FF0000"); + strokeWeight(0); + ellipse(i.x, i.y, i.radius * 1.25, i.radius * 1.25); + break; + case SlowPaddle: + fill("#FF0000"); + strokeWeight(0); + rect(rectX, rectY, rectW, rectH); + break; + case CreateBall: + strokeWeight(sw * 2); + stroke("#00FF00"); + line(i.x - lineLength, i.y, i.x + lineLength, i.y); + line(i.x, i.y - lineLength, i.x, i.y + lineLength); + break; + case UpgradeBricks: + strokeWeight(sw * 2); + stroke("#FF0000"); + line(i.x - lineLength, i.y, i.x + lineLength, i.y); + line(i.x, i.y - lineLength, i.x, i.y + lineLength); + break; + } + } + + //Balls and paddleboard are black + fill(0); + stroke(0); + strokeWeight(strokePadding * 2); + + var p = this.paddle; + + //Draw paddleboard + rect(p.x, p.y, p.width, p.height); + + //Ball doesnt need rounded corners + strokeWeight(0); + + //Draw every ball + for (var b of this.balls) ellipse(b.x, b.y, b.radius * 2, b.radius * 2); + + //Break if game isnt running + if (this.isPaused || !this.isStarted) return; + + //Check collision and other situations of balls + for(var ball1 of this.balls){ + + //Check if ball flew away + if (ball1.isLost()) this.toBeErased.push(ball1); + + //Check collision with frameborders + for (var fb of this.frameBorders){ + var collision = collisionDetection(ball1, fb); + if (collision.isTouching) performCollision(ball1, fb, collision); + } + + //Check collision with paddleboard + var collision = collisionDetection(ball1, p); + if (collision.isTouching) performCollision(ball1, p, collision); + + //Check collision with bricks + for (var brick of this.bricks){ + if (brick.state == 0) continue; + var collision = collisionDetection(ball1, brick); + if (collision.isTouching){ + performCollision(ball1, brick, collision); + if (brick.state > 0) brick.state--; + if (floor(random(this.itemFrequency + 1)) % this.itemFrequency == 0 && brick.state == 0) this.createItem(brick); + if (brick.state == 0) this.totalBricksDestroyed++; + setTotalBricksDestroyed(level, this.totalBricksDestroyed); + } + } + + //Check collision with other balls + for (var ball2 of this.balls){ + if (ball1 == ball2) continue; + var collision = collisionDetection(ball1, ball2); + if (collision.isTouching) performCollision(ball1, ball2, collision); + } + + //Check if velocites go to NaN due to mistakes + if (isNaN(ball1.v.x) || isNaN(ball1.v.y)){ + ball1.x = p.x + p.width / 2; + ball1.y = p.y - ball1.radius - strokePadding * 2; + ball1.v.x = 0; + ball1.v.y = ball1.v.mag; + } + } + + //Checkings for items + for (var item of this.items){ + + var index = this.items.indexOf(item); + + //Check collision with paddleboard + var collision = collisionDetection(item, p); + if (collision.isTouching){ + this.items.splice(index, 1); + switch (item.item){ + case FastBall: + for (var b of this.balls) for (var v in b.v) b.v[v] *= 1.25; + break; + case FastPaddle: + p.v *= 1.25; + break; + case SlowBall: + for (var b of this.balls) for (var v in b.v) b.v[v] *= 0.75; + break; + case SlowPaddle: + p.v *= 0.75; + break; + case CreateBall: + var p = this.paddle; + var radius = (wWidth * 0.02 + wHeight * 0.02) / 2 * random(0.75, 1.25); + var x = p.x + p.width / 2; + var y = p.y - (radius + strokePadding); + var v = (wWidth * 0.01 + wHeight * 0.01) / 3; + var vx = 0; + var vy = v; + this.createBall(x, y, radius, v, vx, vy); + break; + case UpgradeBricks: + for (var b of this.bricks) if (b.state > 0 && b.state < stateCount - 1) b.state++; + break; + } + } + if (item.y - item.radius > wHeight){ + this.items.splice(index, 1); + break; + } + } + + + //Erase all balls out of screen + for (var ball of this.toBeErased){ + var index = this.balls.indexOf(ball); + this.balls.splice(index, 1); + } + this.toBeErased = []; + + //Game is lost of no balls are in there + if (this.balls.length == 0) this.gameLost(); + + //Break if game isnt running once more because previous checkings could've caused a paused game + if (!this.isStarted || this.isPaused) return; + + //Finally move objects forward + for (var b of this.balls) b.move(); + for (var i of this.items) i.move(); + p.move(); + } + + + this.createItem = function(b){ + + var item = floor(random(itemCount)) + 1; + + var bx = b.x; + var by = b.y; + var bw = b.width; + var bh = b.height; + + var x = bx + bw / 2; + var y = by + bh / 2; + var radius = (wHeight + wWidth) / 2 * 0.02; + + this.items.push(new Item(x, y, radius, random(1, 6), item)); + } + + this.createBall = function(x, y, r, v, vx, vy){ + + var b = new Ball(); + + b.x = x; + b.y = y; + b.radius = r; + b.v = {mag: v, x: vx, y: vy}; + + this.balls.push(b); + } + + + this.gameLost = function(){ + this.isPaused = true; + this.isLost = true; + if (!infoIsOpen) openInfo(); + } + + this.gameWon = function(){ + this.isPaused = true; + this.isWon = true; + if (this.levelNum - 1 == levelReached){ + levelReached++; + setCookie("levelReached", String(levelReached), 10); + checkLevelButtons(this.levelNum); + } + if (this.currentTime < getRecordTime(this.levelNum)){ + setRecordTime(this.levelNum, this.currentTime); + } + if (!infoIsOpen) openInfo(); + } + + this.restart = function(){this.setDefault();} + + this.pause = function(paused){ + if (this.isWon || this.isLost) return; + if (paused){ + pauseAnimation(true); + this.isPaused = true; + } else { + pauseAnimation(false); + this.isPaused = false; + if (infoIsOpen) closeInfo(); + } + } + this.start = function(){ + this.isStarted = true; + if (infoIsOpen) closeInfo(); + } + + this.lastTime = new Date().getTime(); + + this.renderTime = function(){ + var d = new Date().getTime(); + var passed = d - this.lastTime; + this.lastTime = d; + + if (!this.isPaused && this.isStarted){ + this.currentTime += passed; + setTotalTimePlayed(getTotalTimePlayed() + passed); + } + + var timeString = toTimeString(this.currentTime); + + $("#timeDiv span").html(timeString); + } +} \ No newline at end of file diff --git a/public/scripts/Main.js b/public/scripts/Main.js new file mode 100644 index 0000000..e3fd5a6 --- /dev/null +++ b/public/scripts/Main.js @@ -0,0 +1,116 @@ +var currentGame = null; +var infoIsMoving = false; +var infoIsOpen = true; +var instructionsOpen = false; +var testMode = false; +var animationDuration = 300; + + +function addLevel(HTMLInputElement, add){ + HTMLInputElement.blur(); + var currentLevelNum = parseInt($("#currentLevel").html()); + var newLevelNum = currentLevelNum + add; + $("#currentLevel").html(newLevelNum); + checkLevelButtons(newLevelNum); + currentGame = new Level(newLevelNum); +} + +function setRekordInfo(){ + for (var i = 1; i <= levelCount; i++){ + + var bricks = getTotalBricksDestroyed(i); + + var time = getRecordTime(i); + time = time == Infinity ? "---" : toTimeString(time); + + $("#rekordInfo table:eq(1) tr:eq(" + i + ") td:eq(0)").text(bricks); + $("#rekordInfo table:eq(1) tr:eq(" + i + ") td:eq(2)").text(time); + } + + var time = toTimeString(getTotalTimePlayed(), true); + var bricks = getTotalBricksDestroyedGlobal(); + + $("#totalTimePlayed").text(time); + $("#totalBricksDestroyed").text(bricks); +} + +function checkLevelButtons(level) { + $("#levelUp").removeAttr("disabled"); + $("#levelUp").css("cursor", "pointer"); + $("#levelDown").removeAttr("disabled"); + $("#levelDown").css("cursor", "pointer"); + if (level == levelCount || level == levelReached + 1){ + $("#levelUp").attr("disabled", "disabled"); + $("#levelUp").css("cursor", "auto"); + } + if (level == 1){ + $("#levelDown").attr("disabled", "disabled"); + $("#levelDown").css("cursor", "auto"); + } +} + +function setInfoStyles(){ + $("#levelLabel, #currentLevel").css("font-size", String($("#levelSelector").outerHeight() * 0.25) + "px"); + $("#levelUp, #levelDown").css("font-size", String($("#levelSelector").outerHeight() * 0.2) + "px"); + $("#closeButton").css({ + "width": String($("#closeButton").outerHeight()) + "px", + "font-size": String($("#closeButton").outerHeight() * 0.6) + "px" + }); + $("#controlsInfo table tr td").css("font-size", String($("#controlsInfo table tr td").innerHeight() * 0.3) + "px"); +} + +function closeInfo(HTMLInputElement){ + try {HTMLInputElement.blur();}catch(e){} + if (infoIsMoving) return; + infoIsMoving = true; + $("#info *").hide(); + $("#info").animate({ + width: 0 + }, animationDuration, function(){ + $("#openButton").show(); + $("#openButton").animate({ + width: 60 + }, animationDuration, function(){ + infoIsMoving = false; + infoIsOpen = false; + }); + }); +} + +function openInfo(HTMLInputElement){ + try {HTMLInputElement.blur();} catch(e) {} + if (infoIsMoving){ + return; + } + infoIsMoving = true; + $("#openButton").animate({ + width: 0 + }, animationDuration, function() { + $("#openButton").hide(); + $("#info").animate({ + width: 450 + }, animationDuration, function() { + $("#info *").show(); + infoIsMoving = false; + infoIsOpen = true; + }); + }); +} + +function pauseAnimation(paused){ + //TODO +} + +function openInstructions(){ + $("#instructions").show(); + instructionsOpen = true; +} + +function closeInstructions(){ + $("#instructions").hide(); + instructionsOpen = false; +} + +function toggleTestMode(){ + testMode = !testMode; +} \ No newline at end of file diff --git a/public/scripts/Objects.js b/public/scripts/Objects.js new file mode 100644 index 0000000..e57b485 --- /dev/null +++ b/public/scripts/Objects.js @@ -0,0 +1,76 @@ +function Paddle(){ + this.isRectangle = true; + this.isPaddle = true; + + this.move = function(){ + if (keyIsDown(LEFT_ARROW) && this.x - strokePadding > 0) this.x -= this.v; + if (keyIsDown(RIGHT_ARROW) && this.x + this.width + strokePadding < wWidth) this.x += this.v; + } +} + +var itemCount = 6; //Change if necessary + +var FastBall = 1; +var FastPaddle = 2; +var SlowBall = 3; +var SlowPaddle = 4; +var CreateBall = 5; +var UpgradeBricks = 6; +function Item(x, y, radius, v, item){ + this.isItem = true; + this.isEllipse = true; + this.x = x; + this.y = y; + this.radius = radius; + this.v = v; + this.item = item; + + this.move = function() {this.y += this.v} +} + + +function Brick(){ + this.isRectangle = true; + this.isBrick = true; +} + + +function Frameborder(x, y, width, height){ + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.isRectangle = true; + this.isFrameborder = true; + this.isBrick = false; +} + + +function Ball(){ + this.isEllipse = true; + + this.calcVelocityX = function(p, vx){ + var divider = (this.x - (p.x - this.radius)) / (p.width + this.radius * 2 + strokePadding * 4); //Number between 0 and 1 + var range = divider - 0.5; //Number between -0.5 and 0.5 + range = range * this.v.mag / 0.5; //Number between -magnitude and magnitude + range += vx; //Number between -(magnitude+abs(vx)) and magnitude+abs(vx) + newVx = range * this.v.mag / (this.v.mag + abs(vx)); //Number between -magnitude and magnitude + return newVx; + } + + this.calcVelocityY = function(){ + var vy = sqrt(pow(this.v.mag, 2) - pow(this.v.x, 2)); //Pythagoras to keep velocity constant + return vy; + } + + this.isLost = function(){ + var b = this; + if (b.y - b.radius > wHeight) return true; + return false; + } + + this.move = function(){ + this.x += this.v.x; + this.y += this.v.y; + } +} \ No newline at end of file diff --git a/public/scripts/Stats.js b/public/scripts/Stats.js new file mode 100644 index 0000000..2914395 --- /dev/null +++ b/public/scripts/Stats.js @@ -0,0 +1,69 @@ +//Only change if added more cases in LevelSwitcher +var levelCount = 5; + +//Global: Amount of reached levels +var levelReached = null; + +//Global: Erases every Cookie and starts Level 1 with 0 levelReached +function deleteMemory(){ + clearStorage(); + currentGame.totalBricksDestroyed = getTotalBricksDestroyed(currentGame.levelNum); + currentGame.recordTime = getRecordTime(currentGame.levelNum); + setRekordInfo(); + $("#currentLevel").text("1"); + checkLevelButtons(1); + currentGame = new Level(1); +} + +//Level: Returns the shortest time ever played on that level +function getRecordTime(level){ + var time = getItem("rekordTime" + level); + time = time == null ? Infinity : parseInt(time); + return time; +} + +//Global: Returns the total time played from every level +function getTotalTimePlayed(){ + var time = getItem("totalTimePlayed"); + time = time == null ? 0 : parseInt(time); + return time; +} + +//Level: Returns the total amount of destroyed bricks of that level +function getTotalBricksDestroyed(level){ + var bricks = getItem("totalBricksDestroyed" + level); + bricks = bricks == null ? 0 : parseInt(bricks); + return bricks; +} + +//Global: Returns the total global amount of destroyed bricks +function getTotalBricksDestroyedGlobal(){ + var totalBricks = 0; + for (var i = 1; i <= levelCount; i++){ + var bricks = getItem("totalBricksDestroyed" + i); + bricks = bricks == null ? 0 : parseInt(bricks); + totalBricks += bricks; + } + return totalBricks; +} + +//Level: Sets a new record time for a specific level +function setRecordTime(level, time){ + //storeItem("rekordTime" + level, String(time), 10); + time = toTimeString(time); + $("#rekordInfo table:eq(1) tr:eq(" + level + ") td:eq(2)").text(time); +} + +//Global: Sets the total time played from every level +function setTotalTimePlayed(time){ + //storeItem("totalTimePlayed", String(time), 10); + time = toTimeString(time, true); + $("#totalTimePlayed").text(time); +} + +//Level: Sets the total amount of destroyed bricks for a specific level +function setTotalBricksDestroyed(level, bricks){ + //storeItem("totalBricksDestroyed" + level, String(bricks), 10); + $("#rekordInfo table:eq(1) tr:eq(" + level + ") td:eq(0)").text(bricks); + $("#totalBricksDestroyed").text(getTotalBricksDestroyedGlobal()); +} \ No newline at end of file diff --git a/public/scripts/Switcher.js b/public/scripts/Switcher.js new file mode 100644 index 0000000..f4fbb94 --- /dev/null +++ b/public/scripts/Switcher.js @@ -0,0 +1,168 @@ +var stateCount = 9; + + +function colorSwitch(state){ + + switch (state){ + case -1: return "#373737"; //Gray - Invincible + case 0: return false; //No brick + case 1: return "#008800"; //Dark green + case 2: return "#33FF33"; //Light green + case 3: return "#99BBFF"; //Light blue + case 4: return "#0000BB"; //Dark blue + case 5: return "#AA00DD"; //Purple + case 6: return "#FF0000"; //Red + case 7: return "#FF7800"; //Orange + case 8: return "#FFFF00"; //Yellow + //Add new colors here + default: return "#373737"; + } +} + + +function tableSwitch(levelNum, level){ + + var c = 0, r = 0; + + switch (levelNum){ + case 1: + case 2: + c = 5; + r = 10; + break; + case 3: + case 4: + c = 7; + r = 12; + break; + case 5: + case 6: + c = 9; + r = 14; + break; + case 7: + case 8: + c = 11; + r = 16; + break; + case 9: + case 10: + c = 13; + r = 18; + break; + case 11: + case 12: + c = 15; + r = 20; + break; + case 13: + case 14: + c = 17; + r = 22; + break; + case 15: + case 16: + c = 19; + r = 24; + break; + case 17: + case 18: + c = 21; + r = 26; + break; + case 19: + case 20: + c = 23; + r = 28; + break; + } + return {cols: c, rows: r}; +} + +function designSwitch(levelNum, c, r){ + + var state = 0 + + switch (levelNum) { + case 1: + if (r == 3 || r == 6 || r == 9) state = 1; + break; + case 2: + if ((c == 1 || c == 3 || c == 5) && r % 2 == 0) state = 1; + break; + case 3: + if (r == 5 || r == 9){ + if (c == 4) state = -1; + else if (c == 3 || c == 5) state = 2; + else state = 1; + } + break; + case 4: + if (r == 9 && c > 2 && c < 6) state = -1; + if (r == 8 && c == 4 || r == 7 && c > 2 && c < 6 || r == 6 && c > 1 && c < 7 || r == 5 && c > 2 && c < 6 || r == 4 && c == 4) state = 2; + break; + case 5: + if (r == 1 || r == 14){ + if (c == 1 || c == 9) state = 3; + if (c == 2 || c == 3 || c == 7 || c == 8) state = 2; + if (c > 3 && c < 7) state = 1; + } + if (r == 2 || r == 13){ + if (c < 3 || c > 7) state = 2; + } + if (r == 3 || r == 12){ + if (c == 1 || c == 9) state = 2; + } + if (r > 3 && r < 12){ + if (c == 1 || c == 9) state = 1; + if (r > 6 && r < 9 && c > 3 && c < 7) state = -1; + } + break; + case 6: + + break; + case 7: + + break; + case 8: + + break; + case 9: + + break; + case 10: + + break; + case 11: + + break; + case 12: + + break; + case 13: + + break; + case 14: + + break; + case 15: + + break; + case 16: + + break; + case 17: + + break; + case 18: + + break; + case 19: + + break; + case 20: + + break; + } + return state; +} \ No newline at end of file diff --git a/public/scripts/WindowEvents.js b/public/scripts/WindowEvents.js new file mode 100644 index 0000000..dba7549 --- /dev/null +++ b/public/scripts/WindowEvents.js @@ -0,0 +1,61 @@ +window.onkeydown = function(e){ + + var key = e.keyCode ? e.keyCode : e.which; + + if (instructionsOpen){ + if (key == ESCAPE){ + closeInstructions(); + e.preventDefault(); + } + return; + } + + switch (key) { + case ESCAPE: + e.preventDefault(); + closeInfo(null); + break; + case TAB: + e.preventDefault(); + openInfo(null); + break; + case 32: //Space + e.preventDefault(); + if (currentGame.isPaused){ + currentGame.pause(false); + } else if (currentGame.isStarted && !currentGame.isPaused) { + currentGame.pause(true); + } + if (!currentGame.isStarted){ + currentGame.start(); + } + break; + case SHIFT: + e.preventDefault(); + if (keyIsDown(68)){ + //Shift + D + deleteMemory(); + } else if (keyIsDown(82)){ + //Shift + R + currentGame.restart(); + } else if (keyIsDown(78)){ + //Shift + N + toggleNotifications(); + } + break; + case 68: //D + e.preventDefault(); + if (keyIsDown(SHIFT)){ + //D + Shift + deleteMemory(); + } + break; + case 82: //R + e.preventDefault(); + if (keyIsDown(SHIFT)){ + //R + Shift + currentGame.restart(); + } + break; + } +} \ No newline at end of file diff --git a/public/scripts/lib/BenjoLibrary.js b/public/scripts/lib/BenjoLibrary.js new file mode 100644 index 0000000..4f93b91 --- /dev/null +++ b/public/scripts/lib/BenjoLibrary.js @@ -0,0 +1,354 @@ +var TOP = 1; +var RIGHT = 2 +var BOTTOM = 3; +var LEFT = 4; +var TOP_RIGHT = 5; +var BOTTOM_RIGHT = 6; +var BOTTOM_LEFT = 7; +var TOP_LEFT = 8; + +var wWidth = window.innerWidth; +var wHeight = window.innerHeight; +var oldWHeight; +var oldWWidth; + +function updateVars(){ + oldWWidth = wWidth; + oldWHeight = wHeight; + wWidth = window.innerWidth; + wHeight = window.innerHeight; +} + +function collisionDetection(obj0, obj1){ + + var sp = strokePadding; + + if (obj0.isEllipse && obj1.isRectangle){ + + //Ball + var b = obj0; + + //Rectangle + var r = obj1; + + for (var i = 0; i < TWO_PI; i += PI / 32){ + + /* Check every borderpoint of the ball beginning + at the top in clock direction up to top again */ + + // Ball Center X + var bcx = b.x; + + // Ball Center Y + var bcy = b.y; + + // Ball Border X + var bbx = b.x + sin(i) * b.radius; + + // Ball Border Y inverted because Y = 0 is the TOP of the screen + var bby = b.y - cos(i) * b.radius; + + // Rectangle Width + var rW = r.width + 2 * sp; + + // Rectangle Height + var rH = r.height + 2 * sp; + + // Rectangle Border X + var rX = r.x - sp; + + // Rectangle Border Y + var rY = r.y - sp; + + // Objects touch + if (bbx > rX && bbx < rX + rW + && bby > rY && bby < rY + rH){ + + // STRAIGHT FACES // + + //Top/Bottom touch + if (bcx > rX && bcx < rX + rW){ + + //Top touch + if (b.v.y > 0) return {isTouching: true, location: TOP}; + + //Bottom touch + if (b.v.y < 0) return {isTouching: true, location: BOTTOM}; + } + + //Left/Right touch + if (bcy > rY && bcy < rY + rH){ + + //Left touch + if (b.v.x > 0) return {isTouching: true, location: LEFT}; + + //Right touch + if (b.v.x < 0) return {isTouching: true, location: RIGHT}; + + } + + // CORNERS // + + // BOTTOM Left/Right + if (i > 0 && i <= PI / 2) return {isTouching: true, location: BOTTOM_LEFT}; + + //LEFT Bottom/Top + if (i > PI / 2 && i <= PI) return {isTouching: true, location: TOP_LEFT}; + + //TOP Left/Right + if (i > PI && i <= PI + PI / 2) return {isTouching: true, location: TOP_RIGHT}; + + //RIGHT Bottom/Top + if (i > PI + PI / 2 && i <= TWO_PI) return {isTouching: true, location: BOTTOM_RIGHT}; + } + } + + } + + if (obj0.isEllipse && obj1.isEllipse){ + + //Ball 1 + var b1 = obj0; + + //Ball 2 + var b2 = obj1; + + //Balls are close to each other + if (b1.x + b1.radius > b2.x - b2.radius + && b1.x - b1.radius < b2.x + b2.radius + && b1.y + b1.radius > b2.y - b2.radius + && b1.y - b1.radius < b2.y + b2.radius){ + + var distance = sqrt(pow(b1.x - b2.x, 2) + pow(b1.y - b2.y, 2)); + + if (distance < b1.radius + b2.radius) return {isTouching: true}; + } + } + return {isTouching: false, location: 0}; +} + +function performCollision(obj0, obj1, collision){ + if (obj0.isEllipse){ + + var ball = obj0; + + //Ball collides with frameborder + if (obj1.isFrameborder){ + switch (collision.location){ + case BOTTOM: + ball.v.y *= -1; + break; + case LEFT: + case RIGHT: + ball.v.x *= -1; + break; + } + if (testMode && collision.location == TOP) ball.v.y *= -1; + ball.move(); + } + + //Ball collides with any brick + if (obj1.isBrick){ + switch (collision.location){ + case TOP: + case BOTTOM: + ball.v.y *= -1; + ball.move(); + return; + case LEFT: + case RIGHT: + ball.v.x *= -1; + ball.move(); + return; + case TOP_LEFT: + var cornerX = obj1.x; + var cornerY = obj1.y; + break; + case TOP_RIGHT: + var cornerX = obj1.x + obj1.width; + var cornerY = obj1.y; + break; + case BOTTOM_LEFT: + var cornerX = obj1.x; + var cornerY = obj1.y + obj1.height; + break; + case BOTTOM_RIGHT: + var cornerX = obj1.x + obj1.width; + var cornerY = obj1.y + obj1.height; + break; + } + + var nx = ball.x - cornerX; + var ny = ball.y - cornerY; + var length = sqrt(nx * nx + ny * ny); + nx /= length; + ny /= length; + + var projection = ball.v.x * nx + ball.v.y * ny; + ball.v.x = ball.v.x - 2 * projection * nx; + ball.v.y = ball.v.y - 2 * projection * ny; + + ball.move(); + + } + + //Ball collides with paddleboard + if (obj1.isPaddle){ + switch (collision.location){ + case TOP: + case TOP_LEFT: + case TOP_RIGHT: + ball.v.x = ball.calcVelocityX(obj1, ball.v.x); + ball.v.y = -ball.calcVelocityY(); + ball.move(); + return; + case LEFT: + case RIGHT: + ball.v.x *= -1; + ball.move(); + return; + case BOTTOM_LEFT: + var cornerX = obj1.x; + var cornerY = obj1.y + obj1.height; + break; + case BOTTOM_RIGHT: + var cornerX = obj1.x + obj1.width; + var cornerY = obj1.y + obj1.height; + break; + } + var nx = ball.x - cornerX; + var ny = ball.y - cornerY; + var length = sqrt(nx * nx + ny * ny); + nx /= length; + ny /= length; + + var projection = ball.v.x * nx + ball.v.y * ny; + ball.v.x = ball.v.x - 2 * projection * nx; + ball.v.y = ball.v.y - 2 * projection * ny; + + ball.move(); + } + + //Ball collides with other ball + if (obj1.isEllipse){ + + //Ball 1 + var b1 = obj0; + + //Ball 2 + var b2 = obj1; + + //Set mass equal to radius of each ball + b1.mass = b1.radius; + b2.mass = b2.radius; + + //Colliding angle of ball 1 to ball 2 using arc tan of both x and y differences + var collisionAngle = atan2((b2.y - b1.y), (b2.x - b1.x)); + + //Converting directions of velocity vector of balls into angles + var d1 = atan2(b1.v.y, b1.v.x); + var d2 = atan2(b2.v.y, b2.v.x); + + //Ignoring mass effects new velocites are simply magnitude multiplied with value of angle differences + var newXspeed1 = b1.v.mag * cos(d1 - collisionAngle); + var newYspeed1 = b1.v.mag * sin(d1 - collisionAngle); + var newXspeed2 = b2.v.mag * cos(d2 - collisionAngle); + var newYspeed2 = b2.v.mag * sin(d2 - collisionAngle); + + //According to the principle of linear momentum, kinetic energy stays the same after collision, so velocities are now related to masses + var finalXspeed1 = ((b1.mass - b2.mass) * newXspeed1 + b2.mass * 2 * newXspeed2) / (b1.mass + b2.mass); + var finalYspeed1 = newYspeed1; + var finalXspeed2 = (b1.mass * 2 * newXspeed1 + (b2.mass - b1.mass) * newXspeed2) / (b1.mass + b2.mass); + var finalYspeed2 = newYspeed2; + + //Values of collisionAngle + var cosAngle = cos(collisionAngle); + var sinAngle = sin(collisionAngle); + + //To also keep velocites relative to pure collisionAngle, subtract sin*x from cos*x and add sin*y to cos*y because coordSystem has y = 0 on the top + var u1x = cosAngle * finalXspeed1 - sinAngle * finalYspeed1; + var u1y = sinAngle * finalXspeed1 + cosAngle * finalYspeed1; + var u2x = cosAngle * finalXspeed2 - sinAngle * finalYspeed2; + var u2y = sinAngle * finalXspeed2 + cosAngle * finalYspeed2; + + //Set new velocities to both balls + b1.v.x = u1x; + b1.v.y = u1y; + b2.v.x = u2x; + b2.v.y = u2y; + + //Update magnitude + b1.v.mag = sqrt(pow(b1.v.x, 2) + pow(b1.v.y, 2)); + b2.v.mag = sqrt(pow(b2.v.x, 2) + pow(b2.v.y, 2)); + + + //Move balls one vx/vy forward to avoid double inverting collision detection + b1.x += b1.v.x; + b1.y += b1.v.y; + b2.x += b2.v.x; + b2.y += b2.v.y; + } + } +} + + + +function toTimeString(time, hoursWanted){ + + var time = floor(time / 10); + + var hs = String(floor(time % 100)); + var fs = String(floor((time / 100) % 60)); + + if (hoursWanted){ + var min = String(floor(((time / 100) / 60) % 60)); + var hr = String(floor(((time / 100) / 60) / 60)); + + if (hs.length < 2) hs = "0" + hs; + if (fs.length < 2) fs = "0" + fs; + if (min.length < 2) min = "0" + min; + if (hr.length < 2) hr = "0" + hr; + + var timeString = hr + ":" + min + ":" + fs + ":" + hs; + } else { + var min = String(floor(((time / 100) / 60) % 60)); + + if (hs.length < 2) hs = "0" + hs; + if (fs.length < 2) fs = "0" + fs; + if (min.length < 2) min = "0" + min; + + var timeString = min + ":" + fs + ":" + hs; + } + + + + return timeString; +} + + +function setCookie(name, value, years){ + var expires = ""; + if (years){ + var date = new Date(); + date.setTime(date.getTime() + (years * 365 * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + value + expires + "; path=/"; +} + +function getCookie(name){ + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++){ + var c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); + } + return null; +} + +function deleteCookies(){ + for (var i = 0; i < arguments.length; i++){ + setCookie(arguments[i], "", -1); + } +} \ No newline at end of file diff --git a/public/scripts/sketch.js b/public/scripts/sketch.js new file mode 100644 index 0000000..65cead0 --- /dev/null +++ b/public/scripts/sketch.js @@ -0,0 +1,173 @@ +var lr; + +function setup(){ + + lr = getItem("levelReached"); + lr = lr == null ? 0 : parseInt(lr); + levelReached = lr + + createCanvas(window.innerWidth, window.innerHeight); + + //Layout Setup + $("#openButton").hide(); + setInfoStyles(); + for (var i = 1; i <= levelCount; i++){ + $("#rekordInfo table:eq(1)").append("" + i + ""); + var bgColor; + var fgColor; + if (i % 2 == 0){ + bgColor = "rgba(200, 0, 0, 0.5)"; + fgColor = "#FFF"; + } + if (i % 2 != 0){ + bgColor = "rgba(0, 200, 0, 0.5)"; + fgColor = "#000"; + } + $("#rekordInfo table:eq(1) tr:eq(" + i + ")").css({ + "background-color": bgColor, + "color": fgColor + }); + } + setRekordInfo(); + + for (var i = 0; i < stateCount; i++){ + $("#colors tr:eq(" + i + ") td:eq(2)").html(""); + $("#brick" + i).attr({ + "width": String(wWidth * 0.3 * 0.6), + "height": String($("#colors").css("font-size").slice(0, 2) * 2) + }); + $("#brick" + i).css({ + "border": strokePadding * 2 + "px solid #000", + "border-radius": strokePadding * 2 + }); + var c = document.getElementById("brick" + i); + var ctx = c.getContext("2d"); + ctx.fillStyle = colorSwitch(i + 1); + ctx.fillRect(0, 0, c.width, c.height); + } + + for (var i = 0; i < itemCount; i++){ + $("#items tr:eq(" + i + ") td:eq(2)").html(""); + $("#item" + i).attr({ + "width": String(wWidth * 0.3 * 0.3), + "height": String($("#items").css("font-size").slice(0, 2) * 3) + }); + + var c = document.getElementById("item" + i); + var ctx = c.getContext("2d"); + + var ballX = c.width / 2; + var ballY = c.height / 2; + var ballR = c.height / 2; + var lw = ballR * 0.2; + + ctx.beginPath(); + ctx.fillStyle = "#000000"; + ctx.arc(ballX, ballY, ballR, 0, TWO_PI); + ctx.fill(); + ctx.closePath(); + + var rectX = ballX - ballR * sin(5 * PI / 8) + lw; + var rectY = ballY + ballR * cos(5 * PI / 8) + lw; + var rectW = ballR * 2 * sin(5 * PI / 8) - lw * 2; + var rectH = ballR * 2 * abs(cos(5 * PI / 8)) - lw * 2; + + ctx.beginPath(); + ctx.lineJoin = "round"; + switch (i + 1){ + case FastBall: + ctx.fillStyle = "#00FF00"; + ctx.arc(ballX, ballY, ballR * 0.625, 0, TWO_PI); + ctx.fill(); + break; + case FastPaddle: + ctx.strokeStyle = "#00FF00"; + ctx.fillStyle = "#00FF00"; + + ctx.moveTo(rectX, rectY); + ctx.lineTo(rectX + rectW, rectY); + ctx.lineTo(rectX + rectW, rectY + rectH); + ctx.lineTo(rectX, rectY + rectH); + ctx.lineTo(rectX, rectY); + ctx.lineTo(rectX + rectW, rectY); + ctx.lineTo(rectX, rectY); + + ctx.stroke(); + ctx.fill(); + break; + case SlowBall: + ctx.fillStyle = "#FF0000"; + ctx.arc(ballX, ballY, ballR * 0.625, 0, TWO_PI); + ctx.fill(); + break; + case SlowPaddle: + ctx.strokeStyle = "#FF0000"; + ctx.fillStyle = "#FF0000"; + + ctx.moveTo(rectX, rectY); + ctx.lineTo(rectX + rectW, rectY); + ctx.lineTo(rectX + rectW, rectY + rectH); + ctx.lineTo(rectX, rectY + rectH); + ctx.lineTo(rectX, rectY); + ctx.lineTo(rectX + rectW, rectY); + ctx.lineTo(rectX, rectY); + + ctx.stroke(); + ctx.fill(); + break; + case CreateBall: + var lineLength = ballR * 0.5; + + ctx.lineWidth = lw; + ctx.strokeStyle = "#00FF00"; + + ctx.moveTo(ballX, ballY); + ctx.lineTo(ballX + lineLength, ballY); + ctx.lineTo(ballX - lineLength, ballY); + ctx.lineTo(ballX, ballY); + + ctx.moveTo(ballX, ballY); + ctx.lineTo(ballX, ballY + lineLength); + ctx.lineTo(ballX, ballY - lineLength); + ctx.lineTo(ballX, ballY); + + ctx.stroke(); + break; + case UpgradeBricks: + var lineLength = ballR * 0.5; + + ctx.lineWidth = lw; + ctx.strokeStyle = "#FF0000"; + + ctx.moveTo(ballX, ballY); + ctx.lineTo(ballX + lineLength, ballY); + ctx.lineTo(ballX - lineLength, ballY); + ctx.lineTo(ballX, ballY); + + ctx.moveTo(ballX, ballY); + ctx.lineTo(ballX, ballY + lineLength); + ctx.lineTo(ballX, ballY - lineLength); + ctx.lineTo(ballX, ballY); + + ctx.stroke(); + break; + case UpgradeBricks: + } + ctx.closePath(); + } + + + //Level Setup + var level = levelReached + 1; + if (level > levelCount) level = levelCount; + currentGame = new Level(level); + $("#currentLevel").html(level); + checkLevelButtons(level); + openInstructions(); +} + +function draw(){ + currentGame.drawShapes(); + currentGame.renderTime(); +} + diff --git a/public/stylesheets/styles.css b/public/stylesheets/styles.css new file mode 100644 index 0000000..b69b41e --- /dev/null +++ b/public/stylesheets/styles.css @@ -0,0 +1,290 @@ +/* General Styles*/ + +body +{ + margin: 0; + padding: 0; +} + +canvas +{ + vertical-align: top; +} + +input +{ + cursor: pointer; +} + + +/* Infolable Styles*/ + +#info +{ + display: block; + position: absolute; + height: 80%; + width: 450px; + top: 5%; + background-color: rgba(50, 50, 50, 0.6); + border-radius: 0 0 40px 0; + box-shadow: 10px 20px 20px #000; + color: #FFF; +} + +#info * +{ + position: absolute; +} + +#levelSelector, #rekordInfo, #controlsInfo +{ + left: 12.5%; + width: 75%; + background-color: rgba(50, 50, 50, 0.6); + border-radius: 10px; + border: 4px solid #000; +} + +#closeButton +{ + right: 0; + height: 5%; + background-color: #000; + border-color: #000; + color: #FFF; + border-radius: 0 0 0 10px; +} + +#openButton +{ + display: block; + position: absolute; + width: 0px; + height: 50px; + top: 5%; + border-radius: 0 5px 5px 0; + background-color: rgba(50, 50, 50, 0.5); + box-shadow: 5px 5px 5px #000; +} + +#openButton input +{ + height: 100%; + width: 100%; + background-color: rgba(0, 0, 0, 0); + border-color: rgba(50, 50, 50, 0.6); + border-left: none; + font-size: 20px; +} + +#levelSelector +{ + top: 5%; + height: 17.5%; +} + +#levelLabel, #currentLevel, #levelUp, #levelDown +{ + width: 50%; + height: 33%; + text-align: center; +} + +#levelLabel +{ + top: 33%; + left: 0; +} + +#currentLevel +{ + top: 33%; + left: 50%; +} + +#levelUp +{ + top: 0; + left: 50%; + background-color: lightgreen; + border-color: lightgreen; + border-radius: 0 10px 0 0; +} + +#levelDown +{ + top: 66%; + left: 50%; + background-color: lightcoral; + border-color: lightcoral; + border-radius: 0 0 10px 0; +} + +#rekordInfo +{ + top: 25%; + height: 30%; + overflow-y: scroll; +} + +#rekordInfo * +{ + position: relative; +} + +#rekordInfo table +{ + width: 95%; + margin-top: 5%; +} + +#rekordInfo table:nth-child(1) tr td +{ + padding: 5%; + color: #FFF; +} + +#rekordInfo table:nth-child(2) +{ + text-align: center; + border-collapse: collapse; + border: 5px solid #000; +} + +#rekordInfo table:nth-child(2) tr td +{ + width: 33%; + padding: 0; +} + +#rekordInfo table:nth-child(2) tr:nth-child(1) td +{ + height: 50px; + background-color: rgba(100, 100, 255, 0.5); + font-weight: bold; +} + +#controlsInfo +{ + top: 57.5%; + height: 37.5%; +} + +#controlsInfo * +{ + position: relative; +} + +#controlsInfo table +{ + color: #FFF; + left: 5%; + height: 100%; + width: 90%; +} + +#infoButton +{ + width: 100%; + height: 75%; + font-size: inherit; + background-color: #FFFF99; +} + + +/* Instructions */ + +#instructions +{ + position: absolute; + display: none; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); +} + +#instructions p +{ + font-size: 23px; +} + +#instructionsWrapper +{ + position: absolute; + width: 30%; + height: 80%; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + background-color: #888; + padding: 5%; + border-radius: 50px; +} + +#instructionsHeader +{ + position: absolute; + text-align: center; + margin: 0; + top: 5%; + left: 0; + width: 100%; +} + +#instructionsContent +{ + position: relative; + height: 100%; + width: 100%; + padding-right: 2.5%; + overflow-y: auto; +} + +#colors, #items +{ + width: 100%; + font-size: 25px; +} + +#colors td, #items td +{ + width: 33%; +} + +#colors td:first-child +{ + text-align: center; +} + + + +/* Other Stuff */ + +#timeDiv +{ + display: block; + position: absolute; + right: 0; + bottom: 0; + width: 200px; + height: 30px; + font-size: 24px; + text-align: center; + color: #BBB; + background-color: rgb(50, 50, 50); + border-radius: 10px 0 0 0; + border-top: 5px solid #000; + border-left: 5px solid #000; +} + +#testMode +{ + display: block; + position: absolute; + right: 0; + top: 0; + height: 50px; +} \ No newline at end of file diff --git a/public/thumbnail.png b/public/thumbnail.png new file mode 100644 index 0000000..fe4ed16 Binary files /dev/null and b/public/thumbnail.png differ