From 3e5c91c5b195707b77cc2827ef5a96185ebd01b7 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 14 Feb 2016 13:42:52 +0100 Subject: [PATCH 001/168] Move JS files into scripts folder --- Queue.js => scripts/Queue.js | 0 mainloop.min.js => scripts/mainloop.min.js | 0 mainloop.min.js.map => scripts/mainloop.min.js.map | 0 zatacka.html | 4 ++-- 4 files changed, 2 insertions(+), 2 deletions(-) rename Queue.js => scripts/Queue.js (100%) rename mainloop.min.js => scripts/mainloop.min.js (100%) rename mainloop.min.js.map => scripts/mainloop.min.js.map (100%) diff --git a/Queue.js b/scripts/Queue.js similarity index 100% rename from Queue.js rename to scripts/Queue.js diff --git a/mainloop.min.js b/scripts/mainloop.min.js similarity index 100% rename from mainloop.min.js rename to scripts/mainloop.min.js diff --git a/mainloop.min.js.map b/scripts/mainloop.min.js.map similarity index 100% rename from mainloop.min.js.map rename to scripts/mainloop.min.js.map diff --git a/zatacka.html b/zatacka.html index a6b6d92..dc9231c 100644 --- a/zatacka.html +++ b/zatacka.html @@ -20,7 +20,7 @@ - - + + From 6fbc77b9d7e7ebc5ec46c1a231561e86b6c9e488 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 14 Feb 2016 13:52:10 +0100 Subject: [PATCH 002/168] Improve readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 01a14bb..666ceeb 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,14 @@ The aim of this project is to create a HTML5/JS remake of the original *Achtung, die Kurve!* from 1995. + +## Play + Open `zatacka.html` in a browser to run the game. + + +## Contribute + +All actual code can be found in `zatacka.js`. The `scripts` folder contains some libraries that the game uses, but those will probably never be modified. + +Everything that happens in the black area is drawn on a canvas. `zatacka.html` and `zatacka.css` provide the scoreboard, chrome, alignment, and scaling. From b4b54e042eee08a78f6fd147ddc10002c4b9b888 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 20 Feb 2016 18:47:51 +0100 Subject: [PATCH 003/168] Try to fix "random death on self" bug Looking carefully at what happened when this bug occurred, I discovered that it did so when going at a close to vertical angle and turning in the opposite direction just as the drawn x value increased. This should go for a near horizontal angle when the drawn y value increased, as well. What happened was that the Kurve would paint on a kuxel it had recently drawn, but it would not understand that it is allowed to do so, and therefore die. This turned out to happen because its this.secondLastDraw and this.thirdLastDraw were never actually updated because of a reference error. --- zatacka.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/zatacka.js b/zatacka.js index a0cb4ed..8de88bd 100644 --- a/zatacka.js +++ b/zatacka.js @@ -140,6 +140,12 @@ function pixelAddress(x, y) { return y*canvasWidth + x; } +function pixelAddressToCoordinates(addr) { + var x = addr % canvasWidth; + var y = (addr - x) / canvasWidth; + return "("+x+", "+y+")"; +} + // Returns true iff the two specified rectangles overlap each other: function isOverlap(left1, top1, left2, top2, thickness) { return left2 > (left1 - thickness) @@ -329,8 +335,8 @@ Player.prototype.occupy = function(left, top) { pixels[pixelAddress(x, y)] = id; } } - this.thirdLastDraw = { "x": this.secondLastDraw.left, "y": this.secondLastDraw.top }; - this.secondLastDraw = { "x": this.lastDraw.left, "y": this.lastDraw.top }; + this.thirdLastDraw = { "x": this.secondLastDraw.x, "y": this.secondLastDraw.y }; + this.secondLastDraw = { "x": this.lastDraw.x, "y": this.lastDraw.y }; this.lastDraw = { "x": left, "y": top }; context.fillStyle = this.color; context.fillRect(left, top, thickness, thickness); @@ -374,6 +380,18 @@ Player.prototype.isCrashingIntoSelf = function(left, top) { var newPixels = this.getNewPixels(left, top); for (var i = 0, len = newPixels.length; i < len; i++) { if (isOccupiedPixelAddress(newPixels[i])) { + // For debugging the seemingly random death on self: + // console.log(this+" dying at ("+left+", "+top+")"); + // console.log(newPixels); + // console.log(this+".lastDraw:"); + // console.log(this.lastDraw); + // console.log(this+".secondLastDraw:"); + // console.log(this.secondLastDraw); + // console.log(this+".thirdLastDraw:"); + // console.log(this.thirdLastDraw); + // for (var n = 0; n < newPixels.length; n++) { + // console.log(pixelAddressToCoordinates(newPixels[n])); + // } return true; } } From 0db2ae29740091eca3ccd821113a4a2da7f59a2d Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 21 Feb 2016 22:18:12 +0100 Subject: [PATCH 004/168] Add basic score mechanics --- zatacka.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/zatacka.js b/zatacka.js index 8de88bd..4bbee90 100644 --- a/zatacka.js +++ b/zatacka.js @@ -344,7 +344,7 @@ Player.prototype.occupy = function(left, top) { Player.prototype.die = function(cause) { console.log(this+" died from "+cause+"."); - game.death(this); + game.deathOf(this); this.alive = false; }; @@ -497,15 +497,24 @@ Game.prototype.start = function() { } }; -Game.prototype.death = function(player) { - for (var i = 0, len = this.livePlayers.length; i < len; i++) { +Game.prototype.deathOf = function(player) { + for (var i = 0; i < this.livePlayers.length; i++) { if (this.livePlayers[i] === player) { + // Remove dead player from livePlayers: this.livePlayers.splice(i, 1); + break; } } + for (var i = 0, len = this.livePlayers.length; i < len; i++) { + this.livePlayers[i].score++; + } + // for (var i = 1, len = this.players.length; i < len; i++) { + // console.log(this.players[i] + ": " +this.players[i].score); + // } }; + window.addEventListener("keyup" , function(event) { Keyboard.onKeyup(event); }, false); window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); }, false); From 65c70cb1ba139444d00c4334c645f32590dc278e Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 21 Feb 2016 23:00:40 +0100 Subject: [PATCH 005/168] Improve scoreboard CSS --- zatacka.css | 49 +++++++++++++++++++++++++------------------------ zatacka.html | 5 ++++- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/zatacka.css b/zatacka.css index a322033..0e5ca8f 100644 --- a/zatacka.css +++ b/zatacka.css @@ -52,36 +52,38 @@ body { min-height: 100%; } -.digit { +#scoreboard { + padding: 0 12px 0 9px; } -.red.digit { - background-image: url("resources/kurve-digits-red.png"); +.digits { + height: 43px; } -.yellow.digit { - background-image: url("resources/kurve-digits-yellow.png"); +.digits > div { + height: 43px; + width: 28px; + display: inline-block; + float: right; } -.orange.digit { - background-image: url("resources/kurve-digits-orange.png"); -} - -.green.digit { - background-image: url("resources/kurve-digits-green.png"); -} - -.pink.digit { - background-image: url("resources/kurve-digits-pink.png"); -} - -.blue.digit { - background-image: url("resources/kurve-digits-blue.png"); -} +.red.digits div { background-image: url("resources/kurve-digits-red.png"); } +.yellow.digits div { background-image: url("resources/kurve-digits-yellow.png"); } +.orange.digits div { background-image: url("resources/kurve-digits-orange.png"); } +.green.digits div { background-image: url("resources/kurve-digits-green.png"); } +.pink.digits div { background-image: url("resources/kurve-digits-pink.png"); } +.blue.digits div { background-image: url("resources/kurve-digits-blue.png"); } -.digit.d1 { - background-position: -28px 0; -} +.digits .d0 { background-position: 0 0; } +.digits .d1 { background-position: -28px 0; } +.digits .d2 { background-position: -56px 0; } +.digits .d3 { background-position: -84px 0; } +.digits .d4 { background-position: -112px 0; } +.digits .d5 { background-position: -140px 0; } +.digits .d6 { background-position: -168px 0; } +.digits .d7 { background-position: -196px 0; } +.digits .d8 { background-position: -224px 0; } +.digits .d9 { background-position: -252px 0; } @@ -102,7 +104,6 @@ body { } .border { border-width: 1px; } aside { width: 77px; height: 480px; } - #scoreboard { padding-left: 9px; } .digit { width: 28px; height: 43px; } } diff --git a/zatacka.html b/zatacka.html index dc9231c..f20bb79 100644 --- a/zatacka.html +++ b/zatacka.html @@ -12,7 +12,10 @@ From 5d84bfb0a7b6b6bc1bfea1c1b74954d899fb78ec Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 29 Feb 2016 10:28:26 +0100 Subject: [PATCH 006/168] Add Round class and more methods to Game --- zatacka.js | 66 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/zatacka.js b/zatacka.js index 4bbee90..5e2d1fc 100644 --- a/zatacka.js +++ b/zatacka.js @@ -462,15 +462,64 @@ Player.prototype.update = function(delta) { +/** + * Round constructor. A Round object holds information about a round. + */ +function Round(maxPlayers) { + this.scoreboard = (new Array(maxPlayers+1)).fill(0); +} + +Round.prototype.finished = false; +Round.prototype.length = null; + +Round.prototype.isFinished = function() { + return this.finished; +}; + +Round.prototype.getLength = function() { + return this.length; +}; + + + + + /** * Game constructor. A Game object holds information about a running game. */ -function Game() { - this.players = new Array(config.maxPlayers+1).fill(null); +function Game(maxPlayers) { + // Length not dependent on number of ACTIVE players; empty player slots are null: + this.players = new Array(maxPlayers+1).fill(null); this.livePlayers = []; - this.scoreboard = (new Array(config.maxPlayers+1)).fill(0); + this.rounds = Game.emptyRoundsArray(maxPlayers); + this.scoreboard = (new Array(maxPlayers+1)).fill(null); } +Game.calculateTargetScore = function(numberOfActivePlayers) { + return (numberOfActivePlayers - 1) * 10; +}; + +Game.emptyRoundsArray = function(maxPlayers) { + var rounds = new Array(maxPlayers + 1); +}; + + +Game.prototype.numberOfActivePlayers = 0; +Game.prototype.targetScore = null; + +Game.prototype.setTargetScore = function(s) { + console.log("Setting target score to "+s+"."); + this.targetScore = s; +}; + +Game.prototype.getTargetScore = function() { + return this.targetScore; +}; + +Game.prototype.getNumberOfActivePlayers = function() { + return this.numberOfActivePlayers; +}; + /** * Adds a player to the game. * @@ -480,18 +529,23 @@ function Game() { * Player number, e.g. RED = P1, YELLOW = P2, ... , BLUE = P6. */ Game.prototype.addPlayer = function(player) { - this.players[player.getID()] = player; - console.log("Added "+player+" as player "+player.getID()+"."); + if (this.players[player.getID()] !== null) { + console.warn("There is already a player with ID "+player.getID()+". It will be replaced."); + } + this.players[player.getID()] = player; + console.log("Added "+player+" as player "+player.getID()+"."); }; Game.prototype.start = function() { // Grab all added players and put them in livePlayers: for (var i = 0, len = this.players.length; i < len; i++) { if (this.players[i] instanceof Player) { + this.numberOfActivePlayers++; this.livePlayers.push(this.players[i]); console.log("Added "+this.players[i]+" to livePlayers."); } } + this.setTargetScore(Game.calculateTargetScore(this.numberOfActivePlayers)); for (i = 0, len = this.livePlayers.length; i < len; i++) { this.livePlayers[i].spawn(); } @@ -518,7 +572,7 @@ Game.prototype.deathOf = function(player) { window.addEventListener("keyup" , function(event) { Keyboard.onKeyup(event); }, false); window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); }, false); -var game = new Game(); +var game = new Game(config.maxPlayers); game.addPlayer(new Player(1)); game.addPlayer(new Player(2)); game.addPlayer(new Player(3)); From f6859682838856dc85db13016fda12dd31a76efe Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 29 Feb 2016 19:44:07 +0100 Subject: [PATCH 007/168] Add GUIController + updateScoreOfPlayer --- zatacka.css | 12 +++++++++++- zatacka.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/zatacka.css b/zatacka.css index 0e5ca8f..7e85a69 100644 --- a/zatacka.css +++ b/zatacka.css @@ -74,7 +74,17 @@ body { .pink.digits div { background-image: url("resources/kurve-digits-pink.png"); } .blue.digits div { background-image: url("resources/kurve-digits-blue.png"); } -.digits .d0 { background-position: 0 0; } +/* Don't display 0: */ +.digits .d0 { + background-position: 0 0; + display: none; +} + +/* Except for at the ones position: */ +.digits .d0:first-child { + display: block; +} + .digits .d1 { background-position: -28px 0; } .digits .d2 { background-position: -56px 0; } .digits .d3 { background-position: -84px 0; } diff --git a/zatacka.js b/zatacka.js index 5e2d1fc..67902bc 100644 --- a/zatacka.js +++ b/zatacka.js @@ -520,6 +520,49 @@ Game.prototype.getNumberOfActivePlayers = function() { return this.numberOfActivePlayers; }; + + +/** + * GUIController constructor. Controls the game chrome. + */ +function GUIController() { + this.scoreboard = document.getElementById("scoreboard"); + if (!(this.scoreboard instanceof HTMLElement)) { + console.error("Scoreboard HTML element could not be found."); + } +} + + +/** + * Updates the displayed score of the specified player to the specified value. + * + * @param {Number} id + * The id of the player whose score is to be updated. + * @param {Number} newScore + * The new score to display. + */ +GUIController.prototype.updateScoreOfPlayer = function(id, newScore) { + if (!(this.scoreboard instanceof HTMLElement)) { + console.error("Scoreboard HTML element could not be found."); + } else { + var scoreboardItem = this.scoreboard.children[id-1]; // minus 1 necessary since players are 1-indexed + var onesDigit = newScore % 10; // digit at the ones position (4 in 14) + var tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) + if (scoreboardItem instanceof HTMLDivElement && scoreboardItem.children[0] instanceof HTMLDivElement && scoreboardItem.children[1] instanceof HTMLDivElement) { + // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. + // First, we have to remove all digit classes: + scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + // Add appropriate classes to ones and tens position, respectively: + scoreboardItem.children[0].classList.add("d"+onesDigit); + scoreboardItem.children[1].classList.add("d"+tensDigit); + } else { + console.error("Could not find HTML scoreboard entry for "+this.players[id].toString()+"."); + } + } +}; + + /** * Adds a player to the game. * @@ -572,6 +615,8 @@ Game.prototype.deathOf = function(player) { window.addEventListener("keyup" , function(event) { Keyboard.onKeyup(event); }, false); window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); }, false); +var GUI = new GUIController(); + var game = new Game(config.maxPlayers); game.addPlayer(new Player(1)); game.addPlayer(new Player(2)); From 6b6161b627f117622f6b64c0d8e1295952d94857 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 29 Feb 2016 19:48:19 +0100 Subject: [PATCH 008/168] Add constants for player IDs --- zatacka.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/zatacka.js b/zatacka.js index 67902bc..157c866 100644 --- a/zatacka.js +++ b/zatacka.js @@ -248,6 +248,14 @@ function Player(id, name, color, keyL, keyR) { } } +// "Constants" for easier use of player IDs: +Player.RED = 1; +Player.YELLOW = 2; +Player.ORANGE = 3; +Player.GREEN = 4; +Player.PINK = 5; +Player.BLUE = 6; + Player.prototype.score = 0; Player.prototype.alive = false; Player.prototype.x = null; @@ -618,12 +626,12 @@ window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); var GUI = new GUIController(); var game = new Game(config.maxPlayers); -game.addPlayer(new Player(1)); -game.addPlayer(new Player(2)); -game.addPlayer(new Player(3)); -game.addPlayer(new Player(4)); -game.addPlayer(new Player(5)); -game.addPlayer(new Player(6)); +game.addPlayer(new Player(Player.RED)); +game.addPlayer(new Player(Player.YELLOW)); +game.addPlayer(new Player(Player.ORANGE)); +game.addPlayer(new Player(Player.GREEN)); +game.addPlayer(new Player(Player.PINK)); +game.addPlayer(new Player(Player.BLUE)); game.start(); // Debugging: From 624e4e8fbc554ad2dbb2e0272bb595cd5802a23e Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 29 Feb 2016 20:12:06 +0100 Subject: [PATCH 009/168] Move GUIController (was in the middle of Game) --- zatacka.js | 82 ++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/zatacka.js b/zatacka.js index 157c866..bb5846e 100644 --- a/zatacka.js +++ b/zatacka.js @@ -529,48 +529,6 @@ Game.prototype.getNumberOfActivePlayers = function() { }; - -/** - * GUIController constructor. Controls the game chrome. - */ -function GUIController() { - this.scoreboard = document.getElementById("scoreboard"); - if (!(this.scoreboard instanceof HTMLElement)) { - console.error("Scoreboard HTML element could not be found."); - } -} - - -/** - * Updates the displayed score of the specified player to the specified value. - * - * @param {Number} id - * The id of the player whose score is to be updated. - * @param {Number} newScore - * The new score to display. - */ -GUIController.prototype.updateScoreOfPlayer = function(id, newScore) { - if (!(this.scoreboard instanceof HTMLElement)) { - console.error("Scoreboard HTML element could not be found."); - } else { - var scoreboardItem = this.scoreboard.children[id-1]; // minus 1 necessary since players are 1-indexed - var onesDigit = newScore % 10; // digit at the ones position (4 in 14) - var tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) - if (scoreboardItem instanceof HTMLDivElement && scoreboardItem.children[0] instanceof HTMLDivElement && scoreboardItem.children[1] instanceof HTMLDivElement) { - // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. - // First, we have to remove all digit classes: - scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - // Add appropriate classes to ones and tens position, respectively: - scoreboardItem.children[0].classList.add("d"+onesDigit); - scoreboardItem.children[1].classList.add("d"+tensDigit); - } else { - console.error("Could not find HTML scoreboard entry for "+this.players[id].toString()+"."); - } - } -}; - - /** * Adds a player to the game. * @@ -620,6 +578,46 @@ Game.prototype.deathOf = function(player) { +/** + * GUIController constructor. Controls the game chrome. + */ +function GUIController() { + this.scoreboard = document.getElementById("scoreboard"); + if (!(this.scoreboard instanceof HTMLElement)) { + console.error("Scoreboard HTML element could not be found."); + } +} + +/** + * Updates the displayed score of the specified player to the specified value. + * + * @param {Number} id + * The id of the player whose score is to be updated. + * @param {Number} newScore + * The new score to display. + */ +GUIController.prototype.updateScoreOfPlayer = function(id, newScore) { + if (!(this.scoreboard instanceof HTMLElement)) { + console.error("Scoreboard HTML element could not be found."); + } else { + var scoreboardItem = this.scoreboard.children[id-1]; // minus 1 necessary since players are 1-indexed + var onesDigit = newScore % 10; // digit at the ones position (4 in 14) + var tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) + if (scoreboardItem instanceof HTMLDivElement && scoreboardItem.children[0] instanceof HTMLDivElement && scoreboardItem.children[1] instanceof HTMLDivElement) { + // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. + // First, we have to remove all digit classes: + scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + // Add appropriate classes to ones and tens position, respectively: + scoreboardItem.children[0].classList.add("d"+onesDigit); + scoreboardItem.children[1].classList.add("d"+tensDigit); + } else { + console.error("Could not find HTML scoreboard entry for "+this.players[id].toString()+"."); + } + } +}; + + window.addEventListener("keyup" , function(event) { Keyboard.onKeyup(event); }, false); window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); }, false); From a89b9a5f0d9439613468737e01feef1808c8478b Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 29 Feb 2016 23:32:11 +0100 Subject: [PATCH 010/168] Add score digits for all players and hide them --- zatacka.css | 4 ++++ zatacka.html | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/zatacka.css b/zatacka.css index 7e85a69..a2a93a4 100644 --- a/zatacka.css +++ b/zatacka.css @@ -58,6 +58,7 @@ body { .digits { height: 43px; + display: none; } .digits > div { @@ -73,6 +74,9 @@ body { .green.digits div { background-image: url("resources/kurve-digits-green.png"); } .pink.digits div { background-image: url("resources/kurve-digits-pink.png"); } .blue.digits div { background-image: url("resources/kurve-digits-blue.png"); } +.active.digits { + display: block; +} /* Don't display 0: */ .digits .d0 { diff --git a/zatacka.html b/zatacka.html index f20bb79..9fd35ab 100644 --- a/zatacka.html +++ b/zatacka.html @@ -13,8 +13,28 @@ From eef04e5cc7a7cc7b94524252c8e7c67c36fa2ed2 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 00:15:43 +0100 Subject: [PATCH 011/168] Add lobby with player controls --- resources/kurve-lobby-controls-blue.png | Bin 0 -> 3011 bytes resources/kurve-lobby-controls-green.png | Bin 0 -> 3002 bytes resources/kurve-lobby-controls-orange.png | Bin 0 -> 2909 bytes resources/kurve-lobby-controls-pink.png | Bin 0 -> 2933 bytes resources/kurve-lobby-controls-red.png | Bin 0 -> 2906 bytes resources/kurve-lobby-controls-yellow.png | Bin 0 -> 3006 bytes resources/kurve-lobby-ready-blue.png | Bin 0 -> 2938 bytes resources/kurve-lobby-ready-green.png | Bin 0 -> 2935 bytes resources/kurve-lobby-ready-orange.png | Bin 0 -> 2933 bytes resources/kurve-lobby-ready-pink.png | Bin 0 -> 2940 bytes resources/kurve-lobby-ready-red.png | Bin 0 -> 2934 bytes resources/kurve-lobby-ready-yellow.png | Bin 0 -> 2933 bytes zatacka.css | 54 ++++++++++++++++++++++ zatacka.html | 28 +++++++++++ 14 files changed, 82 insertions(+) create mode 100644 resources/kurve-lobby-controls-blue.png create mode 100644 resources/kurve-lobby-controls-green.png create mode 100644 resources/kurve-lobby-controls-orange.png create mode 100644 resources/kurve-lobby-controls-pink.png create mode 100644 resources/kurve-lobby-controls-red.png create mode 100644 resources/kurve-lobby-controls-yellow.png create mode 100644 resources/kurve-lobby-ready-blue.png create mode 100644 resources/kurve-lobby-ready-green.png create mode 100644 resources/kurve-lobby-ready-orange.png create mode 100644 resources/kurve-lobby-ready-pink.png create mode 100644 resources/kurve-lobby-ready-red.png create mode 100644 resources/kurve-lobby-ready-yellow.png diff --git a/resources/kurve-lobby-controls-blue.png b/resources/kurve-lobby-controls-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..9dbd27940fa396845ce2088ad216f37af35d8bba GIT binary patch literal 3011 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 FV1hFbo-zOc literal 0 HcmV?d00001 diff --git a/resources/kurve-lobby-controls-green.png b/resources/kurve-lobby-controls-green.png new file mode 100644 index 0000000000000000000000000000000000000000..fa46a4a68c4d830f0753d13a569264305a693486 GIT binary patch literal 3002 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 literal 0 HcmV?d00001 diff --git a/resources/kurve-lobby-controls-orange.png b/resources/kurve-lobby-controls-orange.png new file mode 100644 index 0000000000000000000000000000000000000000..0221e42cc534acf255aa65035949ebcb1f332d6f GIT binary patch literal 2909 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 Hu0mjfutaBF literal 0 HcmV?d00001 diff --git a/resources/kurve-lobby-controls-pink.png b/resources/kurve-lobby-controls-pink.png new file mode 100644 index 0000000000000000000000000000000000000000..c97f72fcde3a41dbb15ece221cde5bd30c92cd00 GIT binary patch literal 2933 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 literal 0 HcmV?d00001 diff --git a/resources/kurve-lobby-ready-green.png b/resources/kurve-lobby-ready-green.png new file mode 100644 index 0000000000000000000000000000000000000000..1e7d7d7bc503d6c007a53efb534690909268628b GIT binary patch literal 2935 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 literal 0 HcmV?d00001 diff --git a/resources/kurve-lobby-ready-orange.png b/resources/kurve-lobby-ready-orange.png new file mode 100644 index 0000000000000000000000000000000000000000..5ca9442267a3370e90ea26123f5e9beb04fe82ce GIT binary patch literal 2933 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 literal 0 HcmV?d00001 diff --git a/resources/kurve-lobby-ready-pink.png b/resources/kurve-lobby-ready-pink.png new file mode 100644 index 0000000000000000000000000000000000000000..283c17233eae9cc53253966a1022162ba104ed2b GIT binary patch literal 2940 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 literal 0 HcmV?d00001 diff --git a/resources/kurve-lobby-ready-yellow.png b/resources/kurve-lobby-ready-yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..daa6949735c7df811e865bb75d8f6dfffab05953 GIT binary patch literal 2933 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 literal 0 HcmV?d00001 diff --git a/zatacka.css b/zatacka.css index a2a93a4..b882654 100644 --- a/zatacka.css +++ b/zatacka.css @@ -30,6 +30,7 @@ body { #border2 { border-color: #717171; + position: relative; /* to allow absolute positioning of #lobby */ } #border3 { @@ -40,6 +41,59 @@ body { border-color: #515151; } +#lobby { + background-color: rgba(0, 0, 0, 0.8); + height: 480px; + left: 0; + margin: 1px; + position: absolute; + top: 0; + width: 559px; + z-index: 20; +} + +#lobby #controls { + list-style-type: none; + margin-left: 81px; + margin-top: 50px; +} + +#lobby #controls li { + height: 50px; +} + +#lobby #controls li div { + float: left; + height: 100%; + background-repeat: no-repeat; +} + +#lobby #controls .controls { + width: 159px; +} + +#lobby #controls .ready { + display: none; + width: 80px; +} + +#lobby #controls .ready.active { + display: block; +} + +#lobby #controls .red.controls { background-image: url("resources/kurve-lobby-controls-red.png"); } +#lobby #controls .red.ready { background-image: url("resources/kurve-lobby-ready-red.png"); } +#lobby #controls .yellow.controls { background-image: url("resources/kurve-lobby-controls-yellow.png"); } +#lobby #controls .yellow.ready { background-image: url("resources/kurve-lobby-ready-yellow.png"); } +#lobby #controls .orange.controls { background-image: url("resources/kurve-lobby-controls-orange.png"); } +#lobby #controls .orange.ready { background-image: url("resources/kurve-lobby-ready-orange.png"); } +#lobby #controls .green.controls { background-image: url("resources/kurve-lobby-controls-green.png"); } +#lobby #controls .green.ready { background-image: url("resources/kurve-lobby-ready-green.png"); } +#lobby #controls .pink.controls { background-image: url("resources/kurve-lobby-controls-pink.png"); } +#lobby #controls .pink.ready { background-image: url("resources/kurve-lobby-ready-pink.png"); } +#lobby #controls .blue.controls { background-image: url("resources/kurve-lobby-controls-blue.png"); } +#lobby #controls .blue.ready { background-image: url("resources/kurve-lobby-ready-blue.png"); } + #canvas { background-color: black; border-color: #828282; diff --git a/zatacka.html b/zatacka.html index 9fd35ab..b905cf9 100644 --- a/zatacka.html +++ b/zatacka.html @@ -7,6 +7,34 @@
+
+
    +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
+
From a7fb762519405f865c6d9bda940ddf1484c808a5 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 00:25:05 +0100 Subject: [PATCH 012/168] Fix max_fps 30 -> 60 --- zatacka.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zatacka.js b/zatacka.js index bb5846e..36a6afc 100644 --- a/zatacka.js +++ b/zatacka.js @@ -650,7 +650,7 @@ MainLoop .setDraw(draw) .setEnd(end) .setSimulationTimestep(1000/config.tickrate) - .setMaxAllowedFPS(30) + .setMaxAllowedFPS(60) .start(); return { From b1ffb1bda4da34ec8dc8163ed0abdf29e574edbc Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 01:39:23 +0100 Subject: [PATCH 013/168] Add lobby player selection functionality --- zatacka.css | 5 +++ zatacka.js | 87 +++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/zatacka.css b/zatacka.css index b882654..0b04e06 100644 --- a/zatacka.css +++ b/zatacka.css @@ -43,6 +43,7 @@ body { #lobby { background-color: rgba(0, 0, 0, 0.8); + display: block; height: 480px; left: 0; margin: 1px; @@ -52,6 +53,10 @@ body { z-index: 20; } +#lobby.hidden { + display: none; +} + #lobby #controls { list-style-type: none; margin-left: 81px; diff --git a/zatacka.js b/zatacka.js index 36a6afc..205a322 100644 --- a/zatacka.js +++ b/zatacka.js @@ -534,17 +534,32 @@ Game.prototype.getNumberOfActivePlayers = function() { * * @param {Player} player * The Player object representing the player. - * @param {Number} playerNumber - * Player number, e.g. RED = P1, YELLOW = P2, ... , BLUE = P6. */ Game.prototype.addPlayer = function(player) { if (this.players[player.getID()] !== null) { console.warn("There is already a player with ID "+player.getID()+". It will be replaced."); } + this.numberOfActivePlayers++; this.players[player.getID()] = player; console.log("Added "+player+" as player "+player.getID()+"."); }; +/** + * Adds a player to the game. + * + * @param {Number} id + * The ID of the player to be removed. + */ +Game.prototype.removePlayer = function(id) { + if (this.players[id] === null) { + console.warn("Cannot remove player "+id+" because they are not in the game."); + } else { + this.numberOfActivePlayers--; + console.log("Removed "+this.players[id]+" (player "+id+")."); + this.players[id] = null; + } +}; + Game.prototype.start = function() { // Grab all added players and put them in livePlayers: for (var i = 0, len = this.players.length; i < len; i++) { @@ -578,15 +593,51 @@ Game.prototype.deathOf = function(player) { -/** - * GUIController constructor. Controls the game chrome. - */ -function GUIController() { - this.scoreboard = document.getElementById("scoreboard"); - if (!(this.scoreboard instanceof HTMLElement)) { - console.error("Scoreboard HTML element could not be found."); +var GUIController = {}; + +GUIController.lobby = document.getElementById("lobby"); +GUIController.controlsList = document.getElementById("controls"); +GUIController.scoreboard = document.getElementById("scoreboard"); + +GUIController.lobbyKeyListener = function(event) { + for (var i = 1; i < config.players.length; i++) { + if (event.keyCode === config.players[i].keyL) { + game.addPlayer(new Player(i)); + GUIController.playerReady(i); + } else if (event.keyCode === config.players[i].keyR) { + game.removePlayer(i); + GUIController.playerUnready(i); + } } -} + if (event.keyCode === KEY.SPACE) { + if (game.getNumberOfActivePlayers() > 0) { + GUIController.startGame(); + } + } +}; + +GUIController.initLobby = function() { + console.log("======== Zatacka Lobby ========"); + document.addEventListener("keydown", GUIController.lobbyKeyListener); +}; + +GUIController.startGame = function() { + console.log("OK, let's go!"); + GUIController.lobby.classList.add("hidden"); + document.removeEventListener("keydown", GUIController.lobbyKeyListener); + game.start(); + MainLoop.start(); +}; + +GUIController.playerReady = function(id) { + var index = id - 1; + GUIController.controlsList.children[index].children[1].classList.add("active"); +}; + +GUIController.playerUnready = function(id) { + var index = id - 1; + GUIController.controlsList.children[index].children[1].classList.remove("active"); +}; /** * Updates the displayed score of the specified player to the specified value. @@ -596,7 +647,7 @@ function GUIController() { * @param {Number} newScore * The new score to display. */ -GUIController.prototype.updateScoreOfPlayer = function(id, newScore) { +GUIController.updateScoreOfPlayer = function(id, newScore) { if (!(this.scoreboard instanceof HTMLElement)) { console.error("Scoreboard HTML element could not be found."); } else { @@ -621,16 +672,9 @@ GUIController.prototype.updateScoreOfPlayer = function(id, newScore) { window.addEventListener("keyup" , function(event) { Keyboard.onKeyup(event); }, false); window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); }, false); -var GUI = new GUIController(); - var game = new Game(config.maxPlayers); -game.addPlayer(new Player(Player.RED)); -game.addPlayer(new Player(Player.YELLOW)); -game.addPlayer(new Player(Player.ORANGE)); -game.addPlayer(new Player(Player.GREEN)); -game.addPlayer(new Player(Player.PINK)); -game.addPlayer(new Player(Player.BLUE)); -game.start(); + +GUIController.initLobby(); // Debugging: // game.players[1].direction = 0; @@ -650,8 +694,7 @@ MainLoop .setDraw(draw) .setEnd(end) .setSimulationTimestep(1000/config.tickrate) - .setMaxAllowedFPS(60) - .start(); + .setMaxAllowedFPS(60); return { "isOccupiedPixel": isOccupiedPixel, From 808164265cd901c8500a5aa3af0781aee2b8b6ad Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 10:33:34 +0100 Subject: [PATCH 014/168] Add --- zatacka.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zatacka.html b/zatacka.html index b905cf9..7851646 100644 --- a/zatacka.html +++ b/zatacka.html @@ -1,5 +1,7 @@ <!DOCTYPE html> +<title>Achtung, die Kurve! +
From 0a1ca767ffe04348f36998bdb8468121e08ddcd5 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 10:44:29 +0100 Subject: [PATCH 015/168] Show score when game starts --- zatacka.css | 8 +++++--- zatacka.js | 25 ++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/zatacka.css b/zatacka.css index 0b04e06..15d3d02 100644 --- a/zatacka.css +++ b/zatacka.css @@ -120,6 +120,10 @@ body { display: none; } +.active.digits { + display: block; +} + .digits > div { height: 43px; width: 28px; @@ -133,9 +137,7 @@ body { .green.digits div { background-image: url("resources/kurve-digits-green.png"); } .pink.digits div { background-image: url("resources/kurve-digits-pink.png"); } .blue.digits div { background-image: url("resources/kurve-digits-blue.png"); } -.active.digits { - display: block; -} + /* Don't display 0: */ .digits .d0 { diff --git a/zatacka.js b/zatacka.js index 205a322..27c32f1 100644 --- a/zatacka.js +++ b/zatacka.js @@ -623,20 +623,39 @@ GUIController.initLobby = function() { GUIController.startGame = function() { console.log("OK, let's go!"); - GUIController.lobby.classList.add("hidden"); + // Hide lobby: + this.lobby.classList.add("hidden"); + // Remove lobby key listener: document.removeEventListener("keydown", GUIController.lobbyKeyListener); + // Show score of active players: + for (var i = 1, len = game.players.length; i < len; i++) { + if (game.players[i] instanceof Player) { + this.showScoreOfPlayer(i); + } + } game.start(); MainLoop.start(); }; +GUIController.showScoreOfPlayer = function(id) { + var index = id - 1; + var scoreboard = this.scoreboard; + if (scoreboard instanceof HTMLElement) { + var scoreboardEntry = scoreboard.children[index]; + if (scoreboardEntry instanceof HTMLElement) { + scoreboardEntry.classList.add("active"); + } + } +}; + GUIController.playerReady = function(id) { var index = id - 1; - GUIController.controlsList.children[index].children[1].classList.add("active"); + this.controlsList.children[index].children[1].classList.add("active"); }; GUIController.playerUnready = function(id) { var index = id - 1; - GUIController.controlsList.children[index].children[1].classList.remove("active"); + this.controlsList.children[index].children[1].classList.remove("active"); }; /** From 710a31bcd41e30152c570676b4051a37e64cf538 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 11:16:07 +0100 Subject: [PATCH 016/168] Add Player.flicker() functionality --- zatacka.js | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/zatacka.js b/zatacka.js index 27c32f1..61e526d 100644 --- a/zatacka.js +++ b/zatacka.js @@ -21,6 +21,8 @@ var config = { maxPlayers: 6, speed: 64, // Kuxels per second turningRadius: 27, // Kuxels (NB: _radius_) + flickerFrequency: 20, // Hz, when spawning + flickerDuration: 830, // ms, when spawning players: [ null, // => very neat logic since Red = P1, Yellow = P2 etc { name: "Red", color: "#FF2800", keyL: KEY["1"], keyR: KEY.Q }, @@ -178,6 +180,15 @@ function generateSpawnDirection() { return randomFloat(config.minSpawnAngle, config.maxSpawnAngle); } +function drawKurveSquare(x, y, color) { + context.fillStyle = color; + context.fillRect(x, y, config.kurveThickness, config.kurveThickness); +} + +function clearKurveSquare(x, y) { + context.clearRect(x, y, config.kurveThickness, config.kurveThickness); +} + /** * Draws all players. @@ -263,9 +274,10 @@ Player.prototype.y = null; Player.prototype.lastX = null; Player.prototype.lastY = null; Player.prototype.direction = 0; -Player.prototype.velocity = config.speed; +Player.prototype.velocity = 0; Player.prototype.keyL = null; Player.prototype.keyR = null; +Player.prototype.flickerTicker = null; Player.prototype.getID = function() { return this.id; @@ -323,16 +335,47 @@ Player.prototype.reset = function() { this.thirdLastDraw = { "x": null, "y": null }; }; +Player.prototype.flicker = function() { + var isVisible = false; + var x = Math.round(this.x - config.kurveThickness/2); + var y = Math.round(this.y - config.kurveThickness/2); + var color = this.color; + this.flickerTicker = setInterval(function() { + if (isVisible) { + clearKurveSquare(x, y); + isVisible = false; + } else { + drawKurveSquare(x, y, color); + isVisible = true; + } + }, 1000/config.flickerFrequency); +}; + +Player.prototype.stopFlickering = function() { + clearInterval(this.flickerTicker); + var x = Math.round(this.x - config.kurveThickness/2); + var y = Math.round(this.y - config.kurveThickness/2); + drawKurveSquare(x, y, this.color); +}; + Player.prototype.spawn = function() { var spawnPosition = generateSpawnPosition(); this.x = spawnPosition.x; this.y = spawnPosition.y; var spawnDirection = generateSpawnDirection(); this.direction = spawnDirection; + // Player should flicker when it spawns: + this.flicker(); + var self = this; + setTimeout(function() { self.stopFlickering(); }, config.flickerDuration); console.log(this+" spawning at ("+Math.round(spawnPosition.x)+", "+Math.round(spawnPosition.y)+") with direction "+Math.round(spawnDirection*180/Math.PI)+" deg."); this.alive = true; }; +Player.prototype.start = function() { + this.velocity = config.speed; +}; + Player.prototype.occupy = function(left, top) { var id = this.id; var right = left + config.kurveThickness; From fcbcb08e18364f6a69a257cc018edb6dbe6ce68b Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 12:18:33 +0100 Subject: [PATCH 017/168] Add debug box --- zatacka.css | 21 +++++++++++++++++++++ zatacka.html | 7 ++++++- zatacka.js | 5 +++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/zatacka.css b/zatacka.css index 15d3d02..148efc3 100644 --- a/zatacka.css +++ b/zatacka.css @@ -18,6 +18,27 @@ body { overflow: hidden; } +#debug { + position: fixed; + background-color: black; + border: 1px white solid; + top: 0; + left: 0; +} + +#debug span { + font-family: monospace; + font-size: 16px; + display: block; +} + +#debug_red { color: #FF2800; } +#debug_yellow { color: #C3C300; } +#debug_orange { color: #FF7900; } +#debug_green { color: #00CB00; } +#debug_pink { color: #DF51B6; } +#debug_blue { color: #00A2CB; } + #wrapper { display: flex; align-items: center; diff --git a/zatacka.html b/zatacka.html index 7851646..0ca3caa 100644 --- a/zatacka.html +++ b/zatacka.html @@ -70,7 +70,12 @@
- + + + + + +
diff --git a/zatacka.js b/zatacka.js index 61e526d..1aac691 100644 --- a/zatacka.js +++ b/zatacka.js @@ -498,6 +498,11 @@ Player.prototype.update = function(delta) { this.direction -= computeAngleChange(); } + // Debugging: + var debugFieldID = "debug_" + this.getName().toLowerCase(); + var debugField = document.getElementById(debugFieldID); + debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y); + this.lastX = this.x; this.lastY = this.y; var theta = this.velocity * delta / 1000; From fdfc92776147d8f0cdf421dc945e575f14f1b8e1 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 12:35:24 +0100 Subject: [PATCH 018/168] Add working spawn functionality --- zatacka.js | 61 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/zatacka.js b/zatacka.js index 1aac691..68ab53d 100644 --- a/zatacka.js +++ b/zatacka.js @@ -369,10 +369,10 @@ Player.prototype.spawn = function() { var self = this; setTimeout(function() { self.stopFlickering(); }, config.flickerDuration); console.log(this+" spawning at ("+Math.round(spawnPosition.x)+", "+Math.round(spawnPosition.y)+") with direction "+Math.round(spawnDirection*180/Math.PI)+" deg."); - this.alive = true; }; Player.prototype.start = function() { + this.alive = true; this.velocity = config.speed; }; @@ -394,7 +394,7 @@ Player.prototype.occupy = function(left, top) { }; Player.prototype.die = function(cause) { - console.log(this+" died from "+cause+"."); + console.log(this+" died at ("+Math.round(this.x)+", "+Math.round(this.y)+") from "+cause+"."); game.deathOf(this); this.alive = false; }; @@ -457,13 +457,12 @@ Player.prototype.isCrashingIntoSelf = function(left, top) { */ Player.prototype.draw = function() { var id = this.id; - var queuedDraws = this.queuedDraws; - var thickness = config.kurveThickness; + var thickness = config.kurveThickness; var currentDraw; var left, top, right, bottom, x, y, pixelAddress; while (this.isAlive() && !this.queuedDraws.isEmpty()) { // Player is alive and there are queued draw operations to handle. - currentDraw = queuedDraws.dequeue(); + currentDraw = this.queuedDraws.dequeue(); left = Math.round(currentDraw.x - thickness/2); top = Math.round(currentDraw.y - thickness/2); if (!this.justDrewAt(left, top)) { @@ -508,7 +507,7 @@ Player.prototype.update = function(delta) { var theta = this.velocity * delta / 1000; this.x = this.x + theta * Math.cos(this.direction); this.y = this.y - theta * Math.sin(this.direction); - if (ticksSinceDraw % maxTicksBeforeDraw === 0) { + if (this.isAlive() && ticksSinceDraw % maxTicksBeforeDraw === 0) { this.queuedDraws.enqueue({"x": this.x, "y": this.y }); } }; @@ -547,6 +546,7 @@ function Game(maxPlayers) { // Length not dependent on number of ACTIVE players; empty player slots are null: this.players = new Array(maxPlayers+1).fill(null); this.livePlayers = []; + this.activePlayers = []; this.rounds = Game.emptyRoundsArray(maxPlayers); this.scoreboard = (new Array(maxPlayers+1)).fill(null); } @@ -559,8 +559,6 @@ Game.emptyRoundsArray = function(maxPlayers) { var rounds = new Array(maxPlayers + 1); }; - -Game.prototype.numberOfActivePlayers = 0; Game.prototype.targetScore = null; Game.prototype.setTargetScore = function(s) { @@ -572,8 +570,20 @@ Game.prototype.getTargetScore = function() { return this.targetScore; }; +// Works in lobby: +Game.prototype.getNumberOfReadyPlayers = function() { + var n = 0; + for (var i = 0, len = this.players.length; i < len; i++) { + if (this.players[i] instanceof Player) { + n++; + } + } + return n; +}; + +// Works after game.start() only: Game.prototype.getNumberOfActivePlayers = function() { - return this.numberOfActivePlayers; + return this.activePlayers.length; }; @@ -587,7 +597,6 @@ Game.prototype.addPlayer = function(player) { if (this.players[player.getID()] !== null) { console.warn("There is already a player with ID "+player.getID()+". It will be replaced."); } - this.numberOfActivePlayers++; this.players[player.getID()] = player; console.log("Added "+player+" as player "+player.getID()+"."); }; @@ -602,7 +611,6 @@ Game.prototype.removePlayer = function(id) { if (this.players[id] === null) { console.warn("Cannot remove player "+id+" because they are not in the game."); } else { - this.numberOfActivePlayers--; console.log("Removed "+this.players[id]+" (player "+id+")."); this.players[id] = null; } @@ -612,15 +620,35 @@ Game.prototype.start = function() { // Grab all added players and put them in livePlayers: for (var i = 0, len = this.players.length; i < len; i++) { if (this.players[i] instanceof Player) { - this.numberOfActivePlayers++; + this.activePlayers.push(this.players[i]); this.livePlayers.push(this.players[i]); console.log("Added "+this.players[i]+" to livePlayers."); } } - this.setTargetScore(Game.calculateTargetScore(this.numberOfActivePlayers)); - for (i = 0, len = this.livePlayers.length; i < len; i++) { - this.livePlayers[i].spawn(); + this.setTargetScore(Game.calculateTargetScore(this.getNumberOfActivePlayers())); + this.spawnPlayers(); +}; + +Game.prototype.spawnPlayers = function() { + var activePlayers = this.activePlayers; + var self = this; + function spawnNextPlayer(next) { + if (activePlayers[next] instanceof Player) { + activePlayers[next].spawn(); + setTimeout(spawnNextPlayer, config.flickerDuration, next+1); + } else { + self.startPlayers(); + } + } + spawnNextPlayer(0); +}; + +Game.prototype.startPlayers = function() { + var self = this; + for (var i = 0, len = this.activePlayers.length; i < len; i++) { + self.activePlayers[i].start(); } + MainLoop.start(); }; Game.prototype.deathOf = function(player) { @@ -658,7 +686,7 @@ GUIController.lobbyKeyListener = function(event) { } } if (event.keyCode === KEY.SPACE) { - if (game.getNumberOfActivePlayers() > 0) { + if (game.getNumberOfReadyPlayers() > 0) { GUIController.startGame(); } } @@ -682,7 +710,6 @@ GUIController.startGame = function() { } } game.start(); - MainLoop.start(); }; GUIController.showScoreOfPlayer = function(id) { From 6eadb0d8c8e9a2e2df4a51af2b4639574e2e4476 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 13:16:26 +0100 Subject: [PATCH 019/168] .digits display: none -> visibility: hidden visibility: hidden is better than display: none since then the digits still take up their place in the flow. --- zatacka.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zatacka.css b/zatacka.css index 148efc3..a1bdce2 100644 --- a/zatacka.css +++ b/zatacka.css @@ -138,11 +138,11 @@ body { .digits { height: 43px; - display: none; + visibility: hidden; } .active.digits { - display: block; + visibility: visible; } .digits > div { From f435c6384947c037b8a73c2a3101ce0d769b094c Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 13:18:15 +0100 Subject: [PATCH 020/168] Remove unnecessary isAlive() call --- zatacka.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zatacka.js b/zatacka.js index 68ab53d..b5c85fa 100644 --- a/zatacka.js +++ b/zatacka.js @@ -507,7 +507,7 @@ Player.prototype.update = function(delta) { var theta = this.velocity * delta / 1000; this.x = this.x + theta * Math.cos(this.direction); this.y = this.y - theta * Math.sin(this.direction); - if (this.isAlive() && ticksSinceDraw % maxTicksBeforeDraw === 0) { + if (ticksSinceDraw % maxTicksBeforeDraw === 0) { this.queuedDraws.enqueue({"x": this.x, "y": this.y }); } }; From 12d2b35444681994d3a2bc1e7cbc9789d5c2ff95 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 14:11:42 +0100 Subject: [PATCH 021/168] Add partially working score updating --- zatacka.css | 7 ++++--- zatacka.js | 14 ++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/zatacka.css b/zatacka.css index a1bdce2..2bc4d3f 100644 --- a/zatacka.css +++ b/zatacka.css @@ -139,6 +139,7 @@ body { .digits { height: 43px; visibility: hidden; + margin-bottom: 37px; } .active.digits { @@ -149,7 +150,7 @@ body { height: 43px; width: 28px; display: inline-block; - float: right; + float: left; } .red.digits div { background-image: url("resources/kurve-digits-red.png"); } @@ -167,8 +168,8 @@ body { } /* Except for at the ones position: */ -.digits .d0:first-child { - display: block; +.digits .d0:last-child { + display: inline-block; } .digits .d1 { background-position: -28px 0; } diff --git a/zatacka.js b/zatacka.js index b5c85fa..692d4b1 100644 --- a/zatacka.js +++ b/zatacka.js @@ -287,6 +287,10 @@ Player.prototype.getName = function() { return this.name; }; +Player.prototype.getScore = function() { + return this.score; +}; + Player.prototype.toString = function() { return this.name; }; @@ -660,7 +664,8 @@ Game.prototype.deathOf = function(player) { } } for (var i = 0, len = this.livePlayers.length; i < len; i++) { - this.livePlayers[i].score++; + this.livePlayers[i].incrementScore(); + GUIController.updateScoreOfPlayer(this.livePlayers[i].getID(), this.livePlayers[i].getScore()); } // for (var i = 1, len = this.players.length; i < len; i++) { // console.log(this.players[i] + ": " +this.players[i].score); @@ -706,6 +711,7 @@ GUIController.startGame = function() { // Show score of active players: for (var i = 1, len = game.players.length; i < len; i++) { if (game.players[i] instanceof Player) { + this.updateScoreOfPlayer(i, 0); this.showScoreOfPlayer(i); } } @@ -753,9 +759,9 @@ GUIController.updateScoreOfPlayer = function(id, newScore) { // First, we have to remove all digit classes: scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - // Add appropriate classes to ones and tens position, respectively: - scoreboardItem.children[0].classList.add("d"+onesDigit); - scoreboardItem.children[1].classList.add("d"+tensDigit); + // Add appropriate classes to tens and ones position, respectively: + scoreboardItem.children[0].classList.add("d"+tensDigit); + scoreboardItem.children[1].classList.add("d"+onesDigit); } else { console.error("Could not find HTML scoreboard entry for "+this.players[id].toString()+"."); } From aea3465ae2e68b691c22f860c6609e13ecb70f6a Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 14:26:49 +0100 Subject: [PATCH 022/168] Move #debug to front (z-index) --- zatacka.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zatacka.css b/zatacka.css index 2bc4d3f..8d13f17 100644 --- a/zatacka.css +++ b/zatacka.css @@ -19,11 +19,12 @@ body { } #debug { - position: fixed; background-color: black; border: 1px white solid; - top: 0; left: 0; + position: fixed; + top: 0; + z-index: 50; } #debug span { From 6ea2d8cfca4476f54c91991df0bbb4c6abb26b72 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 16:11:15 +0100 Subject: [PATCH 023/168] Add competitive and practice mode skeleton --- zatacka.js | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/zatacka.js b/zatacka.js index 692d4b1..bd1b569 100644 --- a/zatacka.js +++ b/zatacka.js @@ -380,6 +380,11 @@ Player.prototype.start = function() { this.velocity = config.speed; }; +Player.prototype.stop = function() { + this.alive = false; + this.velocity = 0; +}; + Player.prototype.occupy = function(left, top) { var id = this.id; var right = left + config.kurveThickness; @@ -553,8 +558,12 @@ function Game(maxPlayers) { this.activePlayers = []; this.rounds = Game.emptyRoundsArray(maxPlayers); this.scoreboard = (new Array(maxPlayers+1)).fill(null); + this.mode = undefined; } +Game.PRACTICE = "practice"; +Game.COMPETITIVE = "competitive"; + Game.calculateTargetScore = function(numberOfActivePlayers) { return (numberOfActivePlayers - 1) * 10; }; @@ -565,11 +574,20 @@ Game.emptyRoundsArray = function(maxPlayers) { Game.prototype.targetScore = null; +Game.prototype.setMode = function(m) { + console.log("Setting game mode to "+m+"."); + this.mode = m; +}; + Game.prototype.setTargetScore = function(s) { console.log("Setting target score to "+s+"."); this.targetScore = s; }; +Game.prototype.getMode = function() { + return this.mode; +}; + Game.prototype.getTargetScore = function() { return this.targetScore; }; @@ -667,9 +685,11 @@ Game.prototype.deathOf = function(player) { this.livePlayers[i].incrementScore(); GUIController.updateScoreOfPlayer(this.livePlayers[i].getID(), this.livePlayers[i].getScore()); } - // for (var i = 1, len = this.players.length; i < len; i++) { - // console.log(this.players[i] + ": " +this.players[i].score); - // } + if (this.livePlayers.length === 1 && this.getMode() === Game.COMPETITIVE) { + console.log("FREEZE; round over"); + } else if (this.livePlayers.length === 0) { + console.log("FREEZE; round over"); + } }; @@ -691,7 +711,9 @@ GUIController.lobbyKeyListener = function(event) { } } if (event.keyCode === KEY.SPACE) { - if (game.getNumberOfReadyPlayers() > 0) { + var numberOfReadyPlayers = game.getNumberOfReadyPlayers(); + if (numberOfReadyPlayers > 0) { + game.setMode(numberOfReadyPlayers === 1 ? Game.PRACTICE : Game.COMPETITIVE); GUIController.startGame(); } } From 0325a0fc7ba34d52f717868e64198080c4e2b2f7 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 17:48:40 +0100 Subject: [PATCH 024/168] Basic next round functionality --- zatacka.js | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/zatacka.js b/zatacka.js index bd1b569..65e47ef 100644 --- a/zatacka.js +++ b/zatacka.js @@ -326,7 +326,6 @@ Player.prototype.setKeybind = function(dir, key) { }; Player.prototype.reset = function() { - this.score = 0; this.alive = false; this.lastY = null; this.lastX = null; @@ -559,6 +558,15 @@ function Game(maxPlayers) { this.rounds = Game.emptyRoundsArray(maxPlayers); this.scoreboard = (new Array(maxPlayers+1)).fill(null); this.mode = undefined; + this.waitingForSpace = false; + var self = this; + this.inGameKeyListener = function(event) { + if (event.keyCode === KEY.SPACE && self.waitingForSpace) { + self.nextRound(); + } else if (event.keyCode === KEY.ESCAPE) { + // handle Esc + } + }; } Game.PRACTICE = "practice"; @@ -647,6 +655,8 @@ Game.prototype.start = function() { console.log("Added "+this.players[i]+" to livePlayers."); } } + var self = this; + document.addEventListener("keydown", self.inGameKeyListener); this.setTargetScore(Game.calculateTargetScore(this.getNumberOfActivePlayers())); this.spawnPlayers(); }; @@ -673,6 +683,30 @@ Game.prototype.startPlayers = function() { MainLoop.start(); }; +Game.prototype.stopPlayers = function() { + var self = this; + for (var i = 0, len = this.activePlayers.length; i < len; i++) { + self.activePlayers[i].stop(); + } +}; + +Game.prototype.clearCanvas = function() { + context.clearRect(0, 0, canvasWidth, canvasHeight); +}; + +Game.prototype.nextRound = function() { + this.clearCanvas(); + for (var i = 1; i < this.activePlayers.length; i++) { + this.activePlayers[i].reset(); + } + this.spawnPlayers(); +}; + +Game.prototype.freeze = function() { + this.stopPlayers(); + this.waitingForSpace = true; +}; + Game.prototype.deathOf = function(player) { for (var i = 0; i < this.livePlayers.length; i++) { if (this.livePlayers[i] === player) { @@ -686,9 +720,11 @@ Game.prototype.deathOf = function(player) { GUIController.updateScoreOfPlayer(this.livePlayers[i].getID(), this.livePlayers[i].getScore()); } if (this.livePlayers.length === 1 && this.getMode() === Game.COMPETITIVE) { - console.log("FREEZE; round over"); + console.log("Round over. "+this.livePlayers[0]+" won."); + this.freeze(); } else if (this.livePlayers.length === 0) { - console.log("FREEZE; round over"); + console.log("Round over."); + this.freeze(); } }; From f4b1f454e34c29641109e8135ca84f2c46c41a17 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 1 Mar 2016 20:30:30 +0100 Subject: [PATCH 025/168] Fix insta-death on new round --- zatacka.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/zatacka.js b/zatacka.js index 65e47ef..dee5a33 100644 --- a/zatacka.js +++ b/zatacka.js @@ -504,7 +504,6 @@ Player.prototype.update = function(delta) { if (Keyboard.isDown(this.keyR)) { this.direction -= computeAngleChange(); } - // Debugging: var debugFieldID = "debug_" + this.getName().toLowerCase(); var debugField = document.getElementById(debugFieldID); @@ -515,7 +514,7 @@ Player.prototype.update = function(delta) { var theta = this.velocity * delta / 1000; this.x = this.x + theta * Math.cos(this.direction); this.y = this.y - theta * Math.sin(this.direction); - if (ticksSinceDraw % maxTicksBeforeDraw === 0) { + if (this.isAlive() && ticksSinceDraw % maxTicksBeforeDraw === 0) { this.queuedDraws.enqueue({"x": this.x, "y": this.y }); } }; @@ -687,17 +686,21 @@ Game.prototype.stopPlayers = function() { var self = this; for (var i = 0, len = this.activePlayers.length; i < len; i++) { self.activePlayers[i].stop(); + self.activePlayers[i].reset(); } }; -Game.prototype.clearCanvas = function() { +Game.prototype.clearField = function() { context.clearRect(0, 0, canvasWidth, canvasHeight); + pixels.fill(0); }; Game.prototype.nextRound = function() { - this.clearCanvas(); - for (var i = 1; i < this.activePlayers.length; i++) { + this.waitingForSpace = false; + this.clearField(); + for (var i = 0; i < this.activePlayers.length; i++) { this.activePlayers[i].reset(); + this.livePlayers[i] = this.activePlayers[i]; } this.spawnPlayers(); }; From c0a19f3dca5bbdeb7916a4d8fe19047906fcd74e Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 2 Mar 2016 12:55:17 +0100 Subject: [PATCH 026/168] Add basic KONEC HRY --- zatacka.css | 13 ++++++++++--- zatacka.html | 1 + zatacka.js | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/zatacka.css b/zatacka.css index 8d13f17..8aadc2b 100644 --- a/zatacka.css +++ b/zatacka.css @@ -63,8 +63,8 @@ body { border-color: #515151; } -#lobby { - background-color: rgba(0, 0, 0, 0.8); +#lobby, #KONEC_HRY { + background-color: black; display: block; height: 480px; left: 0; @@ -72,10 +72,13 @@ body { position: absolute; top: 0; width: 559px; +} + +#lobby { z-index: 20; } -#lobby.hidden { +#lobby.hidden, #KONEC_HRY.hidden { display: none; } @@ -183,6 +186,10 @@ body { .digits .d8 { background-position: -224px 0; } .digits .d9 { background-position: -252px 0; } +#KONEC_HRY { + z-index: 30; + color: white; +} /* ======== 1x ======== */ diff --git a/zatacka.html b/zatacka.html index 0ca3caa..04a368f 100644 --- a/zatacka.html +++ b/zatacka.html @@ -37,6 +37,7 @@
+ diff --git a/zatacka.js b/zatacka.js index dee5a33..6f2c713 100644 --- a/zatacka.js +++ b/zatacka.js @@ -699,6 +699,11 @@ Game.prototype.nextRound = function() { this.waitingForSpace = false; this.clearField(); for (var i = 0; i < this.activePlayers.length; i++) { + console.log(this.getTargetScore()); + if (this.activePlayers[i].getScore() >= this.getTargetScore()) { + this.konecHry(); + return; + } this.activePlayers[i].reset(); this.livePlayers[i] = this.activePlayers[i]; } @@ -710,6 +715,11 @@ Game.prototype.freeze = function() { this.waitingForSpace = true; }; +// Game over: +Game.prototype.konecHry = function() { + GUIController.showKonecHry(); +}; + Game.prototype.deathOf = function(player) { for (var i = 0; i < this.livePlayers.length; i++) { if (this.livePlayers[i] === player) { @@ -738,6 +748,7 @@ var GUIController = {}; GUIController.lobby = document.getElementById("lobby"); GUIController.controlsList = document.getElementById("controls"); GUIController.scoreboard = document.getElementById("scoreboard"); +GUIController.konecHry = document.getElementById("KONEC_HRY"); GUIController.lobbyKeyListener = function(event) { for (var i = 1; i < config.players.length; i++) { @@ -829,6 +840,10 @@ GUIController.updateScoreOfPlayer = function(id, newScore) { } }; +GUIController.showKonecHry = function() { + this.konecHry.classList.remove("hidden"); + this.resetScoreboard(); +}; window.addEventListener("keyup" , function(event) { Keyboard.onKeyup(event); }, false); window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); }, false); From 19632ea8427db940d24b87a922aac2992094da48 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 2 Mar 2016 13:36:39 +0100 Subject: [PATCH 027/168] Handle prac and comp scoreboard+KH properly --- zatacka.js | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/zatacka.js b/zatacka.js index 6f2c713..16c5f10 100644 --- a/zatacka.js +++ b/zatacka.js @@ -557,10 +557,11 @@ function Game(maxPlayers) { this.rounds = Game.emptyRoundsArray(maxPlayers); this.scoreboard = (new Array(maxPlayers+1)).fill(null); this.mode = undefined; - this.waitingForSpace = false; + this.waitingForNextRound = false; + this.gameOver = false; var self = this; this.inGameKeyListener = function(event) { - if (event.keyCode === KEY.SPACE && self.waitingForSpace) { + if (event.keyCode === KEY.SPACE && self.waitingForNextRound) { self.nextRound(); } else if (event.keyCode === KEY.ESCAPE) { // handle Esc @@ -599,6 +600,10 @@ Game.prototype.getTargetScore = function() { return this.targetScore; }; +Game.prototype.isOver = function() { + return this.gameOver; +}; + // Works in lobby: Game.prototype.getNumberOfReadyPlayers = function() { var n = 0; @@ -696,27 +701,28 @@ Game.prototype.clearField = function() { }; Game.prototype.nextRound = function() { - this.waitingForSpace = false; this.clearField(); for (var i = 0; i < this.activePlayers.length; i++) { - console.log(this.getTargetScore()); - if (this.activePlayers[i].getScore() >= this.getTargetScore()) { + if (this.getMode() === Game.COMPETITIVE && this.activePlayers[i].getScore() >= this.getTargetScore()) { + // Someone won the game; display KONEC HRY: this.konecHry(); return; } this.activePlayers[i].reset(); this.livePlayers[i] = this.activePlayers[i]; } + this.waitingForNextRound = false; this.spawnPlayers(); }; Game.prototype.freeze = function() { this.stopPlayers(); - this.waitingForSpace = true; + this.waitingForNextRound = true; }; // Game over: Game.prototype.konecHry = function() { + this.gameOver = true; GUIController.showKonecHry(); }; @@ -781,10 +787,12 @@ GUIController.startGame = function() { // Remove lobby key listener: document.removeEventListener("keydown", GUIController.lobbyKeyListener); // Show score of active players: - for (var i = 1, len = game.players.length; i < len; i++) { - if (game.players[i] instanceof Player) { - this.updateScoreOfPlayer(i, 0); - this.showScoreOfPlayer(i); + if (game.getMode() === Game.COMPETITIVE) { + for (var i = 1, len = game.players.length; i < len; i++) { + if (game.players[i] instanceof Player) { + this.updateScoreOfPlayer(i, 0); + this.showScoreOfPlayer(i); + } } } game.start(); @@ -840,6 +848,16 @@ GUIController.updateScoreOfPlayer = function(id, newScore) { } }; +GUIController.resetScoreboard = function() { + if (!(this.scoreboard instanceof HTMLElement)) { + console.error("Scoreboard HTML element could not be found."); + } else { + for (var i = 0; i < this.scoreboard.children.length; i++) { + scoreboard.children[i].classList.remove("active"); + } + } +}; + GUIController.showKonecHry = function() { this.konecHry.classList.remove("hidden"); this.resetScoreboard(); From 1a3be49135fcaee9c67e9dcb3b8d0418347b7970 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 4 Mar 2016 17:27:30 +0100 Subject: [PATCH 028/168] Make spawnArea a function of spawnMargin --- zatacka.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/zatacka.js b/zatacka.js index 16c5f10..df2f622 100644 --- a/zatacka.js +++ b/zatacka.js @@ -10,14 +10,13 @@ var pixels = new Array(canvasWidth*canvasHeight).fill(0); var KEY = Object.freeze({ BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, PAUSE: 19, CAPS_LOCK: 20, ESCAPE: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40, INSERT: 45, DELETE: 46, "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 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, LEFT_META: 91, RIGHT_META: 92, SELECT: 93, NUMPAD_0: 96, NUMPAD_1: 97, NUMPAD_2: 98, NUMPAD_3: 99, NUMPAD_4: 100, NUMPAD_5: 101, NUMPAD_6: 102, NUMPAD_7: 103, NUMPAD_8: 104, NUMPAD_9: 105, MULTIPLY: 106, ADD: 107, SUBTRACT: 109, DECIMAL: 110, DIVIDE: 111, F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117, F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123, NUM_LOCK: 144, SCROLL_LOCK: 145, SEMICOLON: 186, EQUALS: 187, COMMA: 188, DASH: 189, PERIOD: 190, FORWARD_SLASH: 191, GRAVE_ACCENT: 192, OPEN_BRACKET: 219, BACK_SLASH: 220, CLOSE_BRACKET: 221, SINGLE_QUOTE: 222 }); -var config = { +const config = Object.freeze({ tickrate: 600, // Hz drawrate: 60, // Hz kurveThickness: 3, minSpawnAngle: -Math.PI/2, maxSpawnAngle: Math.PI/2, spawnMargin: 100, - spawnArea: null, maxPlayers: 6, speed: 64, // Kuxels per second turningRadius: 27, // Kuxels (NB: _radius_) @@ -32,9 +31,8 @@ var config = { { name: "Pink", color: "#DF51B6", keyL: KEY.DIVIDE, keyR: KEY.MULTIPLY }, { name: "Blue", color: "#00A2CB", keyL: null, keyR: null } ] -}; +}); -config.spawnArea = computeSpawnArea(config.spawnMargin); var ticksSinceDraw = 0; var maxTicksBeforeDraw = config.tickrate/config.drawrate; @@ -170,9 +168,10 @@ function getPixels(left, top) { } function generateSpawnPosition() { + var spawnArea = computeSpawnArea(config.spawnMargin); return { - x: randomFloat(config.spawnArea.x_min, config.spawnArea.x_max), - y: randomFloat(config.spawnArea.y_min, config.spawnArea.y_max) + x: randomFloat(spawnArea.x_min, spawnArea.x_max), + y: randomFloat(spawnArea.y_min, spawnArea.y_max) }; } From 6779c50b1b6dfb3db951b43c34b0187427a3e8f2 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 7 Mar 2016 12:16:56 +0100 Subject: [PATCH 029/168] Create and include lib/utilities.js --- lib/utilities.js | 20 ++++++++++++++++++++ zatacka.html | 1 + 2 files changed, 21 insertions(+) create mode 100644 lib/utilities.js diff --git a/lib/utilities.js b/lib/utilities.js new file mode 100644 index 0000000..a3a4ccd --- /dev/null +++ b/lib/utilities.js @@ -0,0 +1,20 @@ +Object.typeOf = (function typeOf(global) { + return function(obj) { + if (obj === global) { + return "global"; + } + return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase(); + }; +})(this); + +function isInt(n) { + return Object.typeOf(n) === 'number' && n % 1 === 0; +} + +function log(str) { + console.log("Zatacka: " + str); +} + +function logError(str) { + console.error("Zatacka: " + str); +} diff --git a/zatacka.html b/zatacka.html index 04a368f..3de330d 100644 --- a/zatacka.html +++ b/zatacka.html @@ -81,5 +81,6 @@ + From 093b99c89e8b24fac1faff39fbf668df6638d6b5 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 7 Mar 2016 12:17:30 +0100 Subject: [PATCH 030/168] Better round handling + quit ability --- zatacka.js | 85 +++++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/zatacka.js b/zatacka.js index df2f622..ce79cda 100644 --- a/zatacka.js +++ b/zatacka.js @@ -36,21 +36,6 @@ const config = Object.freeze({ var ticksSinceDraw = 0; var maxTicksBeforeDraw = config.tickrate/config.drawrate; - - -Object.typeOf = (function typeOf(global) { - return function(obj) { - if (obj === global) { - return "global"; - } - return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase(); - }; -})(this); - -function isInt(n) { - return Object.typeOf(n) === 'number' && n % 1 === 0; -} - function init() { } @@ -557,13 +542,19 @@ function Game(maxPlayers) { this.scoreboard = (new Array(maxPlayers+1)).fill(null); this.mode = undefined; this.waitingForNextRound = false; - this.gameOver = false; + this.waitingForKonecHry = false; var self = this; - this.inGameKeyListener = function(event) { - if (event.keyCode === KEY.SPACE && self.waitingForNextRound) { - self.nextRound(); - } else if (event.keyCode === KEY.ESCAPE) { - // handle Esc + this.keyPressedInGame = function(event) { + if (self.waitingForNextRound) { + if (event.keyCode === KEY.SPACE) { + self.nextRound(); + } else if (event.keyCode === KEY.ESCAPE) { + GUIController.escapePressedInGame(); + } + } else if (self.waitingForKonecHry) { + if (event.keyCode === KEY.SPACE) { + self.konecHry(); + } } }; } @@ -599,10 +590,6 @@ Game.prototype.getTargetScore = function() { return this.targetScore; }; -Game.prototype.isOver = function() { - return this.gameOver; -}; - // Works in lobby: Game.prototype.getNumberOfReadyPlayers = function() { var n = 0; @@ -659,8 +646,9 @@ Game.prototype.start = function() { } } var self = this; - document.addEventListener("keydown", self.inGameKeyListener); + document.addEventListener("keydown", self.keyPressedInGame); this.setTargetScore(Game.calculateTargetScore(this.getNumberOfActivePlayers())); + // TODO nextRound()? this.spawnPlayers(); }; @@ -700,28 +688,38 @@ Game.prototype.clearField = function() { }; Game.prototype.nextRound = function() { + this.waitingForNextRound = false; this.clearField(); for (var i = 0; i < this.activePlayers.length; i++) { - if (this.getMode() === Game.COMPETITIVE && this.activePlayers[i].getScore() >= this.getTargetScore()) { - // Someone won the game; display KONEC HRY: - this.konecHry(); - return; - } - this.activePlayers[i].reset(); this.livePlayers[i] = this.activePlayers[i]; } - this.waitingForNextRound = false; this.spawnPlayers(); }; -Game.prototype.freeze = function() { +Game.prototype.endRound = function(winner) { this.stopPlayers(); - this.waitingForNextRound = true; + console.log("Round over." + (winner instanceof Player ? " "+winner+" won." : "")); + var someoneWonTheGame = false; + for (var i = 0; i < this.activePlayers.length; i++) { + if (this.getMode() === Game.COMPETITIVE && this.activePlayers[i].getScore() >= this.getTargetScore()) { + someoneWonTheGame = true; + } + this.activePlayers[i].reset(); // TODO needed? + } + if (someoneWonTheGame) { + this.waitingForKonecHry = true; + } else { + this.waitingForNextRound = true; + } +}; + +Game.prototype.quit = function() { + // TODO not perfect, but works for now: + window.location.reload(); }; // Game over: Game.prototype.konecHry = function() { - this.gameOver = true; GUIController.showKonecHry(); }; @@ -738,11 +736,11 @@ Game.prototype.deathOf = function(player) { GUIController.updateScoreOfPlayer(this.livePlayers[i].getID(), this.livePlayers[i].getScore()); } if (this.livePlayers.length === 1 && this.getMode() === Game.COMPETITIVE) { - console.log("Round over. "+this.livePlayers[0]+" won."); - this.freeze(); + // livePlayers[0] won the round: + this.endRound(this.livePlayers[0]); } else if (this.livePlayers.length === 0) { - console.log("Round over."); - this.freeze(); + // The round is over since everyone is dead: + this.endRound(); } }; @@ -862,6 +860,13 @@ GUIController.showKonecHry = function() { this.resetScoreboard(); }; +GUIController.escapePressedInGame = function() { + // TODO can be made better, but works for now: + if (confirm("Do you really wish to quit?")) { + game.quit(); + } +}; + window.addEventListener("keyup" , function(event) { Keyboard.onKeyup(event); }, false); window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); }, false); From b3da3d16db029d55bb3f78b122a89d500c2c61c4 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 7 Mar 2016 13:50:40 +0100 Subject: [PATCH 031/168] Improve key, round and player ready handling --- zatacka.js | 166 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 66 deletions(-) diff --git a/zatacka.js b/zatacka.js index ce79cda..1a4241d 100644 --- a/zatacka.js +++ b/zatacka.js @@ -21,18 +21,20 @@ const config = Object.freeze({ speed: 64, // Kuxels per second turningRadius: 27, // Kuxels (NB: _radius_) flickerFrequency: 20, // Hz, when spawning - flickerDuration: 830, // ms, when spawning - players: [ - null, // => very neat logic since Red = P1, Yellow = P2 etc - { name: "Red", color: "#FF2800", keyL: KEY["1"], keyR: KEY.Q }, - { name: "Yellow", color: "#C3C300", keyL: KEY.CTRL, keyR: KEY.ALT }, - { name: "Orange", color: "#FF7900", keyL: KEY.M, keyR: KEY.COMMA }, - { name: "Green", color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, - { name: "Pink", color: "#DF51B6", keyL: KEY.DIVIDE, keyR: KEY.MULTIPLY }, - { name: "Blue", color: "#00A2CB", keyL: null, keyR: null } - ] -}); + flickerDuration: 830 // ms, when spawning +}; + +var defaultPlayers = [ + null, // => very neat logic since Red = P1, Yellow = P2 etc + { name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, + { name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, + { name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, + { name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, + { name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, + { name: "Blue" , color: "#00A2CB", keyL: null , keyR: null } +]; +config.spawnArea = computeSpawnArea(config.spawnMargin); var ticksSinceDraw = 0; var maxTicksBeforeDraw = config.tickrate/config.drawrate; @@ -54,6 +56,44 @@ var Keyboard = { } }; +function isPlayerKey(keyCode) { + if (game.isStarted()) { + for (var i = 0; i < game.players.length; i++) { + if (game.players[i] instanceof Player && (keyCode === game.players[i].keyL || keyCode === game.players[i].keyR)) { + return true; + } + } + } else { + for (var i = 0; i < defaultPlayers.length; i++) { + if (defaultPlayers[i] !== null && (keyCode === defaultPlayers[i].keyL || keyCode === defaultPlayers[i].keyR)) { + return true; + } + } + } + return false; +} + +function isReservedKey(keyCode) { + // Explicitly disable Tab to prevent accidental unfocus: + return (keyCode === KEY.TAB || keyCode === KEY.SPACE || keyCode === KEY.ESCAPE || isPlayerKey(keyCode)); +} + +function keyDownHandler(event) { + Keyboard.onKeydown(event); + if (isReservedKey(event.keyCode)) { + event.preventDefault(); + } + if (game.isStarted()) { + game.inGameKeyHandler(event); + } else { + game.lobbyKeyHandler(event); + } +} + +function keyUpHandler(event) { + Keyboard.onKeyup(event); +} + function isOnField(x, y) { return x >= 0 && y >= 0 @@ -230,10 +270,10 @@ function end(framerate, panic) { function Player(id, name, color, keyL, keyR) { if (isInt(id) && id > 0 && id <= config.maxPlayers) { this.id = id; - this.name = name || config.players[id].name || "Player "+id; - this.color = color || config.players[id].color || "white"; - this.keyL = keyL || config.players[id].keyL || null; - this.keyR = keyR || config.players[id].keyR || null; + this.name = name || defaultPlayers[id].name || "Player "+id; + this.color = color || defaultPlayers[id].color || "white"; + this.keyL = keyL || defaultPlayers[id].keyL || null; + this.keyR = keyR || defaultPlayers[id].keyR || null; this.queuedDraws = new Queue(); this.lastDraw = { "x": null, "y": null }; this.secondLastDraw = { "x": null, "y": null }; @@ -541,10 +581,11 @@ function Game(maxPlayers) { this.rounds = Game.emptyRoundsArray(maxPlayers); this.scoreboard = (new Array(maxPlayers+1)).fill(null); this.mode = undefined; + this.started = false; this.waitingForNextRound = false; this.waitingForKonecHry = false; var self = this; - this.keyPressedInGame = function(event) { + this.inGameKeyHandler = function(event) { if (self.waitingForNextRound) { if (event.keyCode === KEY.SPACE) { self.nextRound(); @@ -606,6 +647,30 @@ Game.prototype.getNumberOfActivePlayers = function() { return this.activePlayers.length; }; +Game.prototype.isStarted = function() { + return this.started; +}; + +Game.prototype.lobbyKeyHandler = function(event) { + for (var i = 1; i < defaultPlayers.length; i++) { + if (event.keyCode === defaultPlayers[i].keyL) { + this.addPlayer(new Player(i)); + GUIController.playerReady(i); + } else if (event.keyCode === defaultPlayers[i].keyR) { + this.removePlayer(i); + GUIController.playerUnready(i); + } + } + if (event.keyCode === KEY.SPACE) { + var numberOfReadyPlayers = game.getNumberOfReadyPlayers(); + if (numberOfReadyPlayers > 0) { + game.setMode(numberOfReadyPlayers === 1 ? Game.PRACTICE : Game.COMPETITIVE); + GUIController.gameStarted(); + game.start(); + } + } +}; + /** * Adds a player to the game. @@ -614,11 +679,11 @@ Game.prototype.getNumberOfActivePlayers = function() { * The Player object representing the player. */ Game.prototype.addPlayer = function(player) { - if (this.players[player.getID()] !== null) { - console.warn("There is already a player with ID "+player.getID()+". It will be replaced."); + var id = player.getID(); + if (this.players[id] === null) { + this.players[id] = player; + console.log("Player "+id+" ("+player+") ready!"); } - this.players[player.getID()] = player; - console.log("Added "+player+" as player "+player.getID()+"."); }; /** @@ -628,28 +693,29 @@ Game.prototype.addPlayer = function(player) { * The ID of the player to be removed. */ Game.prototype.removePlayer = function(id) { - if (this.players[id] === null) { - console.warn("Cannot remove player "+id+" because they are not in the game."); - } else { - console.log("Removed "+this.players[id]+" (player "+id+")."); + if (this.players[id] instanceof Player) { + console.log("Player "+id+" ("+this.players[id]+") not ready."); this.players[id] = null; } }; Game.prototype.start = function() { - // Grab all added players and put them in livePlayers: + this.started = true; + // Grab all ready players and put them in activePlayers: for (var i = 0, len = this.players.length; i < len; i++) { if (this.players[i] instanceof Player) { + // Tell GUI controller to show scores if in competitive: + if (this.mode === Game.COMPETITIVE) { + GUIController.updateScoreOfPlayer(i, 0); + GUIController.showScoreOfPlayer(i); + } this.activePlayers.push(this.players[i]); - this.livePlayers.push(this.players[i]); - console.log("Added "+this.players[i]+" to livePlayers."); + console.log("Added "+this.players[i]+" to activePlayers."); } } var self = this; - document.addEventListener("keydown", self.keyPressedInGame); this.setTargetScore(Game.calculateTargetScore(this.getNumberOfActivePlayers())); - // TODO nextRound()? - this.spawnPlayers(); + this.nextRound(); }; Game.prototype.spawnPlayers = function() { @@ -753,46 +819,14 @@ GUIController.controlsList = document.getElementById("controls"); GUIController.scoreboard = document.getElementById("scoreboard"); GUIController.konecHry = document.getElementById("KONEC_HRY"); -GUIController.lobbyKeyListener = function(event) { - for (var i = 1; i < config.players.length; i++) { - if (event.keyCode === config.players[i].keyL) { - game.addPlayer(new Player(i)); - GUIController.playerReady(i); - } else if (event.keyCode === config.players[i].keyR) { - game.removePlayer(i); - GUIController.playerUnready(i); - } - } - if (event.keyCode === KEY.SPACE) { - var numberOfReadyPlayers = game.getNumberOfReadyPlayers(); - if (numberOfReadyPlayers > 0) { - game.setMode(numberOfReadyPlayers === 1 ? Game.PRACTICE : Game.COMPETITIVE); - GUIController.startGame(); - } - } -}; - GUIController.initLobby = function() { console.log("======== Zatacka Lobby ========"); - document.addEventListener("keydown", GUIController.lobbyKeyListener); }; -GUIController.startGame = function() { - console.log("OK, let's go!"); +GUIController.gameStarted = function() { + console.log("Hiding lobby."); // Hide lobby: this.lobby.classList.add("hidden"); - // Remove lobby key listener: - document.removeEventListener("keydown", GUIController.lobbyKeyListener); - // Show score of active players: - if (game.getMode() === Game.COMPETITIVE) { - for (var i = 1, len = game.players.length; i < len; i++) { - if (game.players[i] instanceof Player) { - this.updateScoreOfPlayer(i, 0); - this.showScoreOfPlayer(i); - } - } - } - game.start(); }; GUIController.showScoreOfPlayer = function(id) { @@ -867,8 +901,8 @@ GUIController.escapePressedInGame = function() { } }; -window.addEventListener("keyup" , function(event) { Keyboard.onKeyup(event); }, false); -window.addEventListener("keydown", function(event) { Keyboard.onKeydown(event); }, false); +window.addEventListener("keydown", keyDownHandler, false); +window.addEventListener("keyup" , keyUpHandler , false); var game = new Game(config.maxPlayers); From d0b20194133ce42b076cd909f3d848f74e67f348 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 7 Mar 2016 13:51:44 +0100 Subject: [PATCH 032/168] Add logWarning() --- lib/utilities.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/utilities.js b/lib/utilities.js index a3a4ccd..b7a2742 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -15,6 +15,10 @@ function log(str) { console.log("Zatacka: " + str); } +function logWarning(str) { + console.warn("Zatacka: " + str); +} + function logError(str) { console.error("Zatacka: " + str); } From b8704ab63c36a0e7a9ef3b0299c95c1d3e0982de Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 7 Mar 2016 13:55:17 +0100 Subject: [PATCH 033/168] Replace console.log() with log() --- zatacka.js | 44 ++++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/zatacka.js b/zatacka.js index 1a4241d..5e707f3 100644 --- a/zatacka.js +++ b/zatacka.js @@ -340,12 +340,12 @@ Player.prototype.occupies = function(left, top) { Player.prototype.setKeybind = function(dir, key) { if (dir === LEFT) { this.keyL = key; - console.log("Set LEFT key of "+this.toString()+" to "+key+"."); + log("Set LEFT key of "+this.toString()+" to "+key+"."); } else if (dir === RIGHT) { this.keyR = key; - console.log("Set RIGHT key of "+this.toString()+" to "+key+"."); + log("Set RIGHT key of "+this.toString()+" to "+key+"."); } else { - console.warn("Could not bind "+key+" to "+dir+" because it is not a valid direction."); + logWarning("Could not bind "+key+" to "+dir+" because it is not a valid direction."); } }; @@ -395,7 +395,7 @@ Player.prototype.spawn = function() { this.flicker(); var self = this; setTimeout(function() { self.stopFlickering(); }, config.flickerDuration); - console.log(this+" spawning at ("+Math.round(spawnPosition.x)+", "+Math.round(spawnPosition.y)+") with direction "+Math.round(spawnDirection*180/Math.PI)+" deg."); + log(this+" spawning at ("+Math.round(spawnPosition.x)+", "+Math.round(spawnPosition.y)+") with direction "+Math.round(spawnDirection*180/Math.PI)+" deg."); }; Player.prototype.start = function() { @@ -426,7 +426,7 @@ Player.prototype.occupy = function(left, top) { }; Player.prototype.die = function(cause) { - console.log(this+" died at ("+Math.round(this.x)+", "+Math.round(this.y)+") from "+cause+"."); + log(this+" died at ("+Math.round(this.x)+", "+Math.round(this.y)+") from "+cause+"."); game.deathOf(this); this.alive = false; }; @@ -463,18 +463,6 @@ Player.prototype.isCrashingIntoSelf = function(left, top) { var newPixels = this.getNewPixels(left, top); for (var i = 0, len = newPixels.length; i < len; i++) { if (isOccupiedPixelAddress(newPixels[i])) { - // For debugging the seemingly random death on self: - // console.log(this+" dying at ("+left+", "+top+")"); - // console.log(newPixels); - // console.log(this+".lastDraw:"); - // console.log(this.lastDraw); - // console.log(this+".secondLastDraw:"); - // console.log(this.secondLastDraw); - // console.log(this+".thirdLastDraw:"); - // console.log(this.thirdLastDraw); - // for (var n = 0; n < newPixels.length; n++) { - // console.log(pixelAddressToCoordinates(newPixels[n])); - // } return true; } } @@ -614,12 +602,12 @@ Game.emptyRoundsArray = function(maxPlayers) { Game.prototype.targetScore = null; Game.prototype.setMode = function(m) { - console.log("Setting game mode to "+m+"."); + log("Setting game mode to "+m+"."); this.mode = m; }; Game.prototype.setTargetScore = function(s) { - console.log("Setting target score to "+s+"."); + log("Setting target score to "+s+"."); this.targetScore = s; }; @@ -682,7 +670,7 @@ Game.prototype.addPlayer = function(player) { var id = player.getID(); if (this.players[id] === null) { this.players[id] = player; - console.log("Player "+id+" ("+player+") ready!"); + log("Player "+id+" ("+player+") ready!"); } }; @@ -694,7 +682,7 @@ Game.prototype.addPlayer = function(player) { */ Game.prototype.removePlayer = function(id) { if (this.players[id] instanceof Player) { - console.log("Player "+id+" ("+this.players[id]+") not ready."); + log("Player "+id+" ("+this.players[id]+") not ready."); this.players[id] = null; } }; @@ -710,7 +698,7 @@ Game.prototype.start = function() { GUIController.showScoreOfPlayer(i); } this.activePlayers.push(this.players[i]); - console.log("Added "+this.players[i]+" to activePlayers."); + log("Added "+this.players[i]+" to activePlayers."); } } var self = this; @@ -764,7 +752,7 @@ Game.prototype.nextRound = function() { Game.prototype.endRound = function(winner) { this.stopPlayers(); - console.log("Round over." + (winner instanceof Player ? " "+winner+" won." : "")); + log("Round over." + (winner instanceof Player ? " "+winner+" won." : "")); var someoneWonTheGame = false; for (var i = 0; i < this.activePlayers.length; i++) { if (this.getMode() === Game.COMPETITIVE && this.activePlayers[i].getScore() >= this.getTargetScore()) { @@ -820,11 +808,11 @@ GUIController.scoreboard = document.getElementById("scoreboard"); GUIController.konecHry = document.getElementById("KONEC_HRY"); GUIController.initLobby = function() { - console.log("======== Zatacka Lobby ========"); + log("======== Zatacka Lobby ========"); }; GUIController.gameStarted = function() { - console.log("Hiding lobby."); + log("Hiding lobby."); // Hide lobby: this.lobby.classList.add("hidden"); }; @@ -860,7 +848,7 @@ GUIController.playerUnready = function(id) { */ GUIController.updateScoreOfPlayer = function(id, newScore) { if (!(this.scoreboard instanceof HTMLElement)) { - console.error("Scoreboard HTML element could not be found."); + logError("Scoreboard HTML element could not be found."); } else { var scoreboardItem = this.scoreboard.children[id-1]; // minus 1 necessary since players are 1-indexed var onesDigit = newScore % 10; // digit at the ones position (4 in 14) @@ -874,14 +862,14 @@ GUIController.updateScoreOfPlayer = function(id, newScore) { scoreboardItem.children[0].classList.add("d"+tensDigit); scoreboardItem.children[1].classList.add("d"+onesDigit); } else { - console.error("Could not find HTML scoreboard entry for "+this.players[id].toString()+"."); + logError("Could not find HTML scoreboard entry for "+this.players[id].toString()+"."); } } }; GUIController.resetScoreboard = function() { if (!(this.scoreboard instanceof HTMLElement)) { - console.error("Scoreboard HTML element could not be found."); + logError("Scoreboard HTML element could not be found."); } else { for (var i = 0; i < this.scoreboard.children.length; i++) { scoreboard.children[i].classList.remove("active"); From 433a461ba77add76e67831ba3a0119f78c456203 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 7 Mar 2016 14:33:03 +0100 Subject: [PATCH 034/168] Optimize crash check The game no longer knows whether a player crashed into itself or an opponent, but the crash check is faster and simpler. Since the cause of death can only be seen in the console anyway, this seems like a better solution. --- zatacka.js | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/zatacka.js b/zatacka.js index 5e707f3..4019335 100644 --- a/zatacka.js +++ b/zatacka.js @@ -101,24 +101,6 @@ function isOnField(x, y) { && y+config.kurveThickness <= canvasHeight; } -function isOccupiedByOpponent(left, top, id) { - var x, y; - var right = left + config.kurveThickness; - var bottom = top + config.kurveThickness; - for (y = top; y < bottom; y++) { - for (x = left; x < right; x++) { - if (pixels[pixelAddress(x, y)] > 0 && pixels[pixelAddress(x, y)] !== id) { - return true; - } - } - } - return false; -} - -function isOccupied(left, top) { - return isOccupiedByOpponent(left, top, undefined); -} - function isOccupiedPixel(x, y) { return isOccupiedPixelAddress(pixelAddress(x, y)); } @@ -426,7 +408,7 @@ Player.prototype.occupy = function(left, top) { }; Player.prototype.die = function(cause) { - log(this+" died at ("+Math.round(this.x)+", "+Math.round(this.y)+") from "+cause+"."); + log(this+" "+(cause || "died")+" at ("+Math.round(this.x)+", "+Math.round(this.y)+")."); game.deathOf(this); this.alive = false; }; @@ -459,8 +441,9 @@ Player.prototype.getNewPixels = function(left, top) { return newPixels; }; -Player.prototype.isCrashingIntoSelf = function(left, top) { +Player.prototype.isCrashing = function(left, top) { var newPixels = this.getNewPixels(left, top); + var id = this.getID(); for (var i = 0, len = newPixels.length; i < len; i++) { if (isOccupiedPixelAddress(newPixels[i])) { return true; @@ -489,13 +472,10 @@ Player.prototype.draw = function() { // The new draw position is not identical to the last one. if (!isOnField(left, top)) { // The player wants to draw outside the playing field. - this.die("crashing into the wall"); - } else if (isOccupiedByOpponent(left, top, id)) { - // The player wants to draw on a spot occupied by an opponent. - this.die("crashing into an opponent"); - } else if (this.isCrashingIntoSelf(left, top)) { - // The player wants to draw on a spot occupied by itself. - this.die("crashing into itself"); + this.die("crashed into the wall"); + } else if (this.isCrashing(left, top)) { + // The player wants to draw on a spot occupied by itself or an opponent. + this.die("crashed"); } else { this.occupy(left, top); } From 08fabb82aba85c3c240026a5e1d51943e46ea2fe Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 7 Mar 2016 18:18:25 +0100 Subject: [PATCH 035/168] Add 'constants' DIR_L and DIR_R --- zatacka.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/zatacka.js b/zatacka.js index 4019335..78a11b4 100644 --- a/zatacka.js +++ b/zatacka.js @@ -265,13 +265,8 @@ function Player(id, name, color, keyL, keyR) { } } -// "Constants" for easier use of player IDs: -Player.RED = 1; -Player.YELLOW = 2; -Player.ORANGE = 3; -Player.GREEN = 4; -Player.PINK = 5; -Player.BLUE = 6; +Player.DIR_L = "left"; +Player.DIR_R = "right"; Player.prototype.score = 0; Player.prototype.alive = false; @@ -320,10 +315,10 @@ Player.prototype.occupies = function(left, top) { }; Player.prototype.setKeybind = function(dir, key) { - if (dir === LEFT) { + if (dir === this.constructor.DIR_L) { this.keyL = key; log("Set LEFT key of "+this.toString()+" to "+key+"."); - } else if (dir === RIGHT) { + } else if (dir === this.constructor.DIR_R) { this.keyR = key; log("Set RIGHT key of "+this.toString()+" to "+key+"."); } else { From e1d922abc18b8e5c6f26d3c4268063d7663882ec Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 8 Mar 2016 00:40:29 +0100 Subject: [PATCH 036/168] Add hole timers No holes are actually drawn yet or even considered in any way by the game. The timers only provide info about when there *should* be a hole. --- zatacka.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/zatacka.js b/zatacka.js index 78a11b4..3ec6996 100644 --- a/zatacka.js +++ b/zatacka.js @@ -21,8 +21,12 @@ const config = Object.freeze({ speed: 64, // Kuxels per second turningRadius: 27, // Kuxels (NB: _radius_) flickerFrequency: 20, // Hz, when spawning - flickerDuration: 830 // ms, when spawning -}; + flickerDuration: 830, // ms, when spawning + minHoleDistance: 90, // Kuxels + maxHoleDistance: 300, // Kuxels + minHoleLength: 8, // Kuxels + maxHoleLength: 12 // Kuxels +}); var defaultPlayers = [ null, // => very neat logic since Red = P1, Yellow = P2 etc @@ -34,7 +38,6 @@ var defaultPlayers = [ { name: "Blue" , color: "#00A2CB", keyL: null , keyR: null } ]; -config.spawnArea = computeSpawnArea(config.spawnMargin); var ticksSinceDraw = 0; var maxTicksBeforeDraw = config.tickrate/config.drawrate; @@ -109,6 +112,14 @@ function isOccupiedPixelAddress(addr) { return pixels[addr] > 0; } +function randomHoleDistance() { + return Math.round(randomFloat(config.minHoleDistance, config.maxHoleDistance)); +} + +function randomHoleLength() { + return randomFloat(config.minHoleLength, config.maxHoleLength); +} + /** @@ -279,6 +290,8 @@ Player.prototype.velocity = 0; Player.prototype.keyL = null; Player.prototype.keyR = null; Player.prototype.flickerTicker = null; +Player.prototype.hole = false; +Player.prototype.holeTimer = null; Player.prototype.getID = function() { return this.id; @@ -300,6 +313,10 @@ Player.prototype.isAlive = function() { return this.alive; }; +Player.prototype.isHoly = function() { + return this.hole; +}; + Player.prototype.occupies = function(left, top) { var x, y; var right = left + config.kurveThickness; @@ -337,6 +354,9 @@ Player.prototype.reset = function() { this.lastDraw = { "x": null, "y": null }; this.secondLastDraw = { "x": null, "y": null }; this.thirdLastDraw = { "x": null, "y": null }; + this.hole = false; + clearTimeout(this.holeTimer); + this.holeTimer = null; }; Player.prototype.flicker = function() { @@ -375,9 +395,43 @@ Player.prototype.spawn = function() { log(this+" spawning at ("+Math.round(spawnPosition.x)+", "+Math.round(spawnPosition.y)+") with direction "+Math.round(spawnDirection*180/Math.PI)+" deg."); }; +// Even if the player should be going faster, the average +// *distance* between the holes will always be the same: +Player.prototype.randomHoleInterval = function() { + return randomHoleDistance() / this.velocity; +}; + +// So will the average hole size: +Player.prototype.randomHoleDuration = function() { + return randomHoleLength() / this.velocity; +}; + +Player.prototype.beginHole = function(self) { + if (self.isAlive()) { + self.hole = true; + var t = self.randomHoleDuration(); + self.holeTimer = setTimeout(self.endHole, t*1000, self); + } +}; + +Player.prototype.endHole = function(self) { + self.hole = false; + if (self.isAlive()) { + self.startHoleTimer(); + } +}; + +Player.prototype.startHoleTimer = function() { + var self = this; + var t = this.randomHoleInterval(); + log(new Date().getTime() % 100000+" | "+t+" seconds until "+this+"'s next hole."); // for debugging + self.holeTimer = setTimeout(self.beginHole, t*1000, self); +}; + Player.prototype.start = function() { this.alive = true; this.velocity = config.speed; + this.startHoleTimer(); }; Player.prototype.stop = function() { @@ -733,7 +787,7 @@ Game.prototype.endRound = function(winner) { if (this.getMode() === Game.COMPETITIVE && this.activePlayers[i].getScore() >= this.getTargetScore()) { someoneWonTheGame = true; } - this.activePlayers[i].reset(); // TODO needed? + this.activePlayers[i].reset(); } if (someoneWonTheGame) { this.waitingForKonecHry = true; From 86b2756dc97f09e3ab9c20176efa6f199bfcfc0a Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 8 Mar 2016 14:50:05 +0100 Subject: [PATCH 037/168] Add holes (including #heads overlay) --- zatacka.css | 11 +++++++++-- zatacka.html | 5 +++-- zatacka.js | 22 +++++++++++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/zatacka.css b/zatacka.css index 8aadc2b..14022c4 100644 --- a/zatacka.css +++ b/zatacka.css @@ -63,8 +63,7 @@ body { border-color: #515151; } -#lobby, #KONEC_HRY { - background-color: black; +.overlay { display: block; height: 480px; left: 0; @@ -74,6 +73,10 @@ body { width: 559px; } +#lobby, #KONEC_HRY { + background-color: black; +} + #lobby { z-index: 20; } @@ -130,6 +133,10 @@ body { z-index: 10; } +#heads { + z-index: 12; +} + #left, #scoreboard { z-index: 5; box-sizing: border-box; diff --git a/zatacka.html b/zatacka.html index 3de330d..dabc55f 100644 --- a/zatacka.html +++ b/zatacka.html @@ -9,7 +9,7 @@
-
+
  • @@ -37,7 +37,8 @@
- + +
diff --git a/zatacka.js b/zatacka.js index 3ec6996..5b6b666 100644 --- a/zatacka.js +++ b/zatacka.js @@ -4,6 +4,8 @@ var Zatacka = (function(window, document) { var canvas = document.getElementById("canvas"); var context = canvas.getContext("2d"); +var heads = document.getElementById("heads"); +var headsContext = heads.getContext("2d"); var canvasWidth = canvas.width; var canvasHeight = canvas.height; var pixels = new Array(canvasWidth*canvasHeight).fill(0); @@ -206,6 +208,10 @@ function clearKurveSquare(x, y) { context.clearRect(x, y, config.kurveThickness, config.kurveThickness); } +function clearHeads() { + headsContext.clearRect(0, 0, canvasWidth, canvasHeight); +} + /** * Draws all players. @@ -228,6 +234,7 @@ function draw(interpolationPercentage) { * The amount of time since the last update, in seconds. */ function update(delta) { + clearHeads(); for (var i = 0, len = game.livePlayers.length; i < len; i++) { game.livePlayers[i].update(delta); } @@ -439,7 +446,13 @@ Player.prototype.stop = function() { this.velocity = 0; }; +Player.prototype.drawHead = function(left, top) { + headsContext.fillStyle = this.color; + headsContext.fillRect(left, top, config.kurveThickness, config.kurveThickness); +}; + Player.prototype.occupy = function(left, top) { + // TODO thickness var id = this.id; var right = left + config.kurveThickness; var bottom = top + config.kurveThickness; @@ -526,10 +539,17 @@ Player.prototype.draw = function() { // The player wants to draw on a spot occupied by itself or an opponent. this.die("crashed"); } else { - this.occupy(left, top); + // Only draw body if not holy: + if (!this.isHoly()) { + this.occupy(left, top); + } } } } + if (this.isAlive()) { + // Always draw the head of the Kurve, but only once per draw() call: + this.drawHead(left, top); + } }; /** From aab64769263f4f8acd1a53591b38bdf0d325dab2 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 8 Mar 2016 17:48:59 +0100 Subject: [PATCH 038/168] Add KONEC HRY digits and background --- resources/kurve-digits-blue.png | Bin 4100 -> 4326 bytes resources/kurve-digits-green.png | Bin 4096 -> 4376 bytes resources/kurve-digits-orange.png | Bin 4094 -> 4319 bytes resources/kurve-digits-pink.png | Bin 4105 -> 4331 bytes resources/kurve-digits-red.png | Bin 4095 -> 4320 bytes resources/kurve-digits-yellow.png | Bin 4095 -> 4321 bytes resources/kurve-konec-hry.png | Bin 0 -> 5658 bytes zatacka.css | 85 +++++++++++++++++++++--------- zatacka.html | 29 +++++++++- 9 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 resources/kurve-konec-hry.png diff --git a/resources/kurve-digits-blue.png b/resources/kurve-digits-blue.png index 8784097826dc142d5b6e0366e788441c5c4a4d78..6bd24a79d66ff0f3e9301f8ea077c7f97d8bf9f7 100644 GIT binary patch delta 1609 zcmV-P2DbTxAm$;E7YaED1^@s6?Flriu_2xce+CgrL_t(|+U#9RlHD)}G%6n{*U9B_ zog9j6ryTn|iBAJERLvqYu`vi*jU;0_{`@@z!k2&3ffshx%OCzmAYBoDwemA7^-=m;A`TfBpBnQ>c(8e;<+jR`;e4f48!CMpq=zpg}@wua`ghA?7J~Jm}_c ze-<&rU&e4+$7wWh^ds@8D7efzjSL2JSb7_^5}3*e5qduY&Qe$L52Xpe?ot1*#{{Z!>WDcG=^GmdA#m)ND9vq#v{=* zfq_VTG>g&t8O^f1_0dACKdse7jRz`TL?u!NrVtghZ?&P4pV{%6SwGx?2^*Kv-fNp) z6Zdf($1x@xELoed1gN_jlwO!nNN|bXy;=bZX?%LIo$HS##!&I88SjtzK9VySe^+$< zGV^%q;R1=sX#YKiOnt22}Z#Jm(#z0Fl+oH$d^=0vy)H&=_ZngN0bCtzi-T)NJ z$VkRs_yJtJSHn>?GAVU`!*! zsRE3Yz&|N`btWGu)H#^U;9_b2(=%)IT<^&V6R#qB$rSf$fT>XHzqM>bZzF5Be?9u> zREbXy5M_anR-VHZ$WF(E;0+TU#a^lb>ddo}0nn@@AqB-oq>m-XCfF!Wf4O3blm)O5 z;KGi{A!)GW#jY&ddVEUk?C(=q0Dx8NTDSZ<4HLP_mZ~Q4z5A-7n1C7Mj8@SLTAS$r@;?f4pe7I?)`5yz81*=}r#7atA7(0Nw&*%qRX)AONF3i4xTV z7rE{f2}Y=P92NX_y&nPN<=czJqH^;zT7TI|vh3n;QNZv_WQ+Euk-dOd?n%ISnE@G* z-$wF)ib`q)0tcbHpXtt()-YVPFZv~f~%h6^p{_H*YVNBnwA zgGBqd>T4k;pN@?(fQtr<)$#3BeV*Tvra=3;lrAi1a>sju0OD(ah+0^?s4E$}w>4w$ zV;sVKA1?#|1RdpKe~E*(ue&G4j=A*vT+3rM43nvYq$TURgT?m!PH|3$ek*+H`q4q{ z#!~nNzHC72vHJ=z(t6tCWx!piv-Sj}w;#0k*VwnJTeIHt7Iuxu8&SRwgG8z_)38Ae zI_nco><8`r-?-Sh<0oJtV|WS!r(#>jJ3zbx844bpT^o04f7dglz(xi9h;H4{8I zfRP|!^`5bGL;ZL&+jjsL?MzL;7i!-^y4)nRoSsXmq`Q$ zmMT7#^pAm-hnf<}>+=i|l$IV6yv$4y;AhFCqyf&T5*~T79C;liCeMdKkgbKX)NrY-DX86 z1Un^%w_Rv`$e4Tr8V@@OpyR*KmPD3DQ2}|UmjFQ-^|}HTNj9`yGxl+i-s8Z3G15fe z+fMSfw6p-_TwoA0^P(4MWzH5XQ z4)QO0O@uZbzb&O1OiN44mJB9JklUMYoZ+L^n9#d&Wb9AW2kuw4!kfXgw6rvX$;e!y zcOjy7k*fDdiS{S@{$BQxFn*fBw6wG|gUP~NtY@gKYa&(cYbng|o58fSw74>u)m3Hh z6TIk}GuMkzpmqgT?bO!tS$93EuPLGJgXn1n)6&x7CjSNiy!)c8_Tq&>00000NkvXX Hu0mjf99tdR delta 1381 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 FV1kC>A5#DT delta 1377 zcmV-n1)lntB7h)}7YZu~1^@s6qZgMuu_2xce+4T^L_t(|+U#9jk{%%l6w4#!{ujuh z$m>^=`B9)jRIBzO*;PiMTTq9g^0!DDU;a(SzcE-YzxwzYVsOM?Bz->rz6}UPO>Cz0 zsDObl)eAY#n;pg=Ljqu-KDE&YDIDFmDJ7?M4Q#sEIP#$-GW+$F3#7Gn9X9``l z$qnUo4`ya^v7G0l4%Y$4Pe+UjqfhAOQ zWm(qa4cN24Ph~*>R;6oU_-ckg&T^!x0Y18}s&~<(O#2mg73NH;u1y}jyj9h+_8Vyg z^w_T^3A>{}a*Zm;*m176O08dn91W{IZYKZs^R5b*iM;P#jD5O^%t}9m)qJYPKwdR+ z@tBh}(PVkiZgin3MP79cf0eoufLQKG<&EGiNXC5OuL&G5`jaS8HE=!mog&G|s~krL zy9;3U40Kh_yQ~9u9A1OrheWm$JD!$%gX8KiC*y0e|W2#D~L<{e#;?=qvNcvg_wLg zHfjQ#)M2cyZ;#4DeoLAH=XX-+#H~%Pcq9pUd=1be7uIg-N^G}VG4^wfLx}gfApv-V zqnr#l-t#-v6Ju>IfBin!u&t^gn2wX2HtW8F#qs@0eojSy6yDXp9;b#yDZGI$51}2~ zeG-f`pGLe4xJu}(y#a~VgLZt){6=MK_Iq}Tu03!g!h1CUQkIp54RX+4o$$nZ(2l?3 zX6K5ZfOWB96FN@DvW|Czctkv{u00000NkvXXu0mjfnDm#} diff --git a/resources/kurve-digits-orange.png b/resources/kurve-digits-orange.png index 81bbc14ce7b9fc65fdc0f60169093db05c41c11d..99c8a603d494478d926345018ded631f79e58a3c 100644 GIT binary patch delta 1602 zcmV-I2EF)P5*ve{)krcX{&z@(FaM?kFYK(BKm3kBx+45)v1R69*Xzlg#CqKkI1&;^a{LUg~_{$hh zf9p7n29ACu9u)*RI}!0An``4*W3I*Kr|0%<_bf=*IWtpl2!H zPp4!c$DsGnA-Nw317}6o6(-94Rm<3Jf2`ftKg=2242j;m3Wa{^Y!n~1*Kkw zWh##j=fs!#g}`<*;2vZM04(&UmVJ%Ud5U#QM`(P1Jax;zd*#e&D{tbsImR#*77TfYoSfKHB~>0C{E zQtHUgEOr4TD(;U|F*86^yrlubf1{ksT*meI9`|c7y~{CQ${`OxAiM@ zOJ3~CvaQFb#LoUcl?4D;wXSu`uhTG*t8A%i65qS8D!OS>rS*!t4s#}5*OIfBPhIt_ z<3`GW9gTPpP5oAI*|{8T_Tta}!)3hOL43row=_t!kE^~G zV)E(OCLrvI!Icwt~*$4-|rOXbm+Ijr>-9z)NU+=U*O9I zv>v;!03)rZJzfUfg*t0bKzjQ@dw-36tGYGoJ#S&xh`bTy`!GnPDl-im)S$CI;lzH> z-v5n@ojZO47BYsXFmNiib-V+_JCLE^!P&KOmv%iv3T#xse~;)!Zr7ml`0|`E7QODkiIT>`{@HC-bV9IGa(LT? z)`yJAC!q1LlK?vYe6}RAG>QtyJG}%5%Ba^Bs7SJ*?V7QVgY+H;{)>?&`rdYux22^8 zDCYu$n5hpl&V=9W^R~-dUo^v@B;$y(k2^k_!L+oreG*9a&0tzuTDD{`QG(pweB%rswZ??rl_O(+qCRlHvK8J8rlqB&8B9jz61@u%wTo1} zM@qCm(f9YVkA(5l45p=}r5Q{X=3+fVWnB}gYF|rXhTjaPrKQD{!K|(-d!OJ%*Subg z0<|l!ENZ8=me0EDQGHDbZ68EWGnkf^7B~4B0PH`atWiCJ9{>OV07*qoM6N<$g5L2P Aq5uE@ delta 1375 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 diff --git a/resources/kurve-digits-red.png b/resources/kurve-digits-red.png index a576b7678ae0c49589a2d083d221eb02bbe424cd..71dc263d62c8bcda2399324dd124c38e9c370ec1 100644 GIT binary patch delta 1603 zcmV-J2E6(IAK)R77YaED1^@s6?Flriu_2xce+COlL_t(|+U#9Ra^)}xG%6ov?ths% z4%wz0`#p(I12R-)F_YLB1g%DrG4a1cB7FHb9e819z5L;K1kx4ZS1UiWQZFU&=WpBD z@LpA7bIp(c|Ni+YR7jKmACdf4_offOx3YIeS0vD&K|*VS#aQovA&KA0b-UXd_*_CCkH)C z`F=Vj133o0hYrd8NEkRPx~||7F>8|3b!?!+o8|EY8(IZ7%`@;~4z9$DLoK*GUUxbqg=Y!lk!YI0KqNkz z#c2JEW?A0)Xd%|0)@q{00~If#5-9^yhzi=b+EB^Q?0C(rAMU_}jZ10owN0;y`#6r{ z7!wYbtW8(~)LjiqFH9&TxJ2(>tpJ5IKE2q^^+yw9sQA>3_s4u6$r+3*x_+5?e?0YY zfy8aVe%GYpBEuNXJc6A!8&rB@pe31Y(PQxXviMBu9Cj+VTKvYj%Hl3>019PfBx5i9 z050CE;iwv!lsY`m{~-Jv&0enSBh3n(7mVqT($Le+VAsT;?*a$M?8jgXvw4`BDyf019#TCp+*|0Y*yT zpA^12lMfW?986|#v9$l`nKgQ@_vD0$SCPGBihDJ{RH*geTDGCLk+s{u9({DG#HR;{ zvcN|x&*2JWr(;6!hKY`1FVz5b=2^)AXjYPtf?^}m$C6_cY!s(lu|&!Oe^>}`VaMc< zG+6RtSC(x(J|%Yc_o*xZz^ZkvTYjB}iCkq%Rg?JMeO1v-lPaxO+;x~U>AIGjy?pAb zXB{_E2K0E{C_=lVlH|%N$T)DWr%J6~gj}W7h?B{`uS0hQtOVY7FUCHdL}q0iy7hb- z#)-Tub3xc-4K!6=v|F8Me~v@mbtzZvisq6aOd>fYF~siRyuiTz85D zBUC$%3VyrZkAU&=?ZskIxp^9`zw9Jgc5%2UV0b37MSIi8Ucf8&Bw)PEfQ-m*BY8kY zB{c;nsAg7@bduj)JPs>n@|`v7z3a>;lHmXd89phZ1(!-|6jr|KfA`PgN-nF{LLy4) zk8v{!0>N9rWp(y#fWRhJ#91XYTT?%8m2K*`g3He3XtNi8_8%_eXnpnY9R7nU=*<2^wD@ijn1Ev#MCm5klnnz8pW4q?8J z7Xko+j&iZYLEG2ee-mTJT>5>k<*^!u$<#s8l6Bp|V*7rlIHyCu6+U(S=%99EDf|Lo zHlX#`eFYe4J?-%_;4aizdjiti58C@{>|52XS?_rZyGG=VDBp)cB2}4b*q{cT^$92T zgZBP!T{y`7Bmx6V6`xA_ z$3V+NO^M|7d4>o|OAiTNW~K=6vt&}z0B2MQk33n9ybcnR=ffcK!+J%<@<7tRl_ru? zLVst1r0T3oe_!mfNcL~#N@YJ@fpNyCQd?)@(!`hNgt6##2TqhU7WU6>v!WA%osz@b zF0?*mOg;gPhn)n_@#nK8k)=^oK;G#kKu|`#u0TbS4QOE4T{fWN6mwhCRpJp&EEiKJpvM?9x87k|VNLBk<3N!p>FfA=Dt_)^%RoVLlFS_RS zVic%dErC@#wY7ZKU61N(N@)8adYZwsw6wU%&j64lqO3e!FZlof002ovPDHLkV1oW1 B2~+?8 delta 1376 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 W;bUp$K~*lmeVVE_R7#Ysp8f}GYxZ;i literal 0 HcmV?d00001 diff --git a/zatacka.css b/zatacka.css index 14022c4..e44719a 100644 --- a/zatacka.css +++ b/zatacka.css @@ -147,10 +147,20 @@ body { padding: 0 12px 0 9px; } +#KONEC_HRY { + z-index: 30; + background-image: url("resources/kurve-konec-hry.png"); +} + +#KONEC_HRY #results { + list-style-type: none; + margin-top: 80px; + margin-left: 250px; +} + +/* General rules for digits: */ .digits { - height: 43px; visibility: hidden; - margin-bottom: 37px; } .active.digits { @@ -158,23 +168,12 @@ body { } .digits > div { - height: 43px; - width: 28px; display: inline-block; float: left; } -.red.digits div { background-image: url("resources/kurve-digits-red.png"); } -.yellow.digits div { background-image: url("resources/kurve-digits-yellow.png"); } -.orange.digits div { background-image: url("resources/kurve-digits-orange.png"); } -.green.digits div { background-image: url("resources/kurve-digits-green.png"); } -.pink.digits div { background-image: url("resources/kurve-digits-pink.png"); } -.blue.digits div { background-image: url("resources/kurve-digits-blue.png"); } - - /* Don't display 0: */ .digits .d0 { - background-position: 0 0; display: none; } @@ -183,21 +182,57 @@ body { display: inline-block; } -.digits .d1 { background-position: -28px 0; } -.digits .d2 { background-position: -56px 0; } -.digits .d3 { background-position: -84px 0; } -.digits .d4 { background-position: -112px 0; } -.digits .d5 { background-position: -140px 0; } -.digits .d6 { background-position: -168px 0; } -.digits .d7 { background-position: -196px 0; } -.digits .d8 { background-position: -224px 0; } -.digits .d9 { background-position: -252px 0; } +.red.digits div { background-image: url("resources/kurve-digits-red.png"); } +.yellow.digits div { background-image: url("resources/kurve-digits-yellow.png"); } +.orange.digits div { background-image: url("resources/kurve-digits-orange.png"); } +.green.digits div { background-image: url("resources/kurve-digits-green.png"); } +.pink.digits div { background-image: url("resources/kurve-digits-pink.png"); } +.blue.digits div { background-image: url("resources/kurve-digits-blue.png"); } -#KONEC_HRY { - z-index: 30; - color: white; +/* Scoreboard digits: */ +#scoreboard .digits { + height: 43px; + margin-bottom: 37px; +} + +#scoreboard .digits > div { + height: 43px; + width: 28px; } +#scoreboard .digits .d0 { background-position: 0px 0; } +#scoreboard .digits .d1 { background-position: -28px 0; } +#scoreboard .digits .d2 { background-position: -56px 0; } +#scoreboard .digits .d3 { background-position: -84px 0; } +#scoreboard .digits .d4 { background-position: -112px 0; } +#scoreboard .digits .d5 { background-position: -140px 0; } +#scoreboard .digits .d6 { background-position: -168px 0; } +#scoreboard .digits .d7 { background-position: -196px 0; } +#scoreboard .digits .d8 { background-position: -224px 0; } +#scoreboard .digits .d9 { background-position: -252px 0; } + +/* KONEC HRY digits: */ +#KONEC_HRY .digits { + height: 14px; + margin-bottom: 26px; +} + +#KONEC_HRY .digits > div { + height: 14px; + width: 16px; +} + +#KONEC_HRY .digits .d0 { background-position: 0px -43px; } +#KONEC_HRY .digits .d1 { background-position: -28px -43px; } +#KONEC_HRY .digits .d2 { background-position: -56px -43px; } +#KONEC_HRY .digits .d3 { background-position: -84px -43px; } +#KONEC_HRY .digits .d4 { background-position: -112px -43px; } +#KONEC_HRY .digits .d5 { background-position: -140px -43px; } +#KONEC_HRY .digits .d6 { background-position: -168px -43px; } +#KONEC_HRY .digits .d7 { background-position: -196px -43px; } +#KONEC_HRY .digits .d8 { background-position: -224px -43px; } +#KONEC_HRY .digits .d9 { background-position: -252px -43px; } + /* ======== 1x ======== */ /* diff --git a/zatacka.html b/zatacka.html index dabc55f..4ede353 100644 --- a/zatacka.html +++ b/zatacka.html @@ -37,7 +37,34 @@
- +
From 4bdca277aadd53aed74200442d622138d0894c96 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 8 Mar 2016 17:55:51 +0100 Subject: [PATCH 039/168] Make results show up upon KONEC HRY As of this commit, the results list is updated every time the scoreboard is updated. Another possibility is to update the results only once, when the game ends. --- zatacka.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/zatacka.js b/zatacka.js index 5b6b666..bb6d55b 100644 --- a/zatacka.js +++ b/zatacka.js @@ -854,6 +854,7 @@ var GUIController = {}; GUIController.lobby = document.getElementById("lobby"); GUIController.controlsList = document.getElementById("controls"); GUIController.scoreboard = document.getElementById("scoreboard"); +GUIController.results = document.getElementById("results"); GUIController.konecHry = document.getElementById("KONEC_HRY"); GUIController.initLobby = function() { @@ -875,6 +876,12 @@ GUIController.showScoreOfPlayer = function(id) { scoreboardEntry.classList.add("active"); } } + if (results instanceof HTMLElement) { + var resultsEntry = results.children[index]; + if (resultsEntry instanceof HTMLElement) { + resultsEntry.classList.add("active"); + } + } }; GUIController.playerReady = function(id) { @@ -900,9 +907,12 @@ GUIController.updateScoreOfPlayer = function(id, newScore) { logError("Scoreboard HTML element could not be found."); } else { var scoreboardItem = this.scoreboard.children[id-1]; // minus 1 necessary since players are 1-indexed + var resultsItem = this.results.children[id-1]; // minus 1 necessary since players are 1-indexed var onesDigit = newScore % 10; // digit at the ones position (4 in 14) var tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) - if (scoreboardItem instanceof HTMLDivElement && scoreboardItem.children[0] instanceof HTMLDivElement && scoreboardItem.children[1] instanceof HTMLDivElement) { + + // Scoreboard: + if (scoreboardItem instanceof HTMLElement && scoreboardItem.children[0] instanceof HTMLElement && scoreboardItem.children[1] instanceof HTMLElement) { // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. // First, we have to remove all digit classes: scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); @@ -911,7 +921,20 @@ GUIController.updateScoreOfPlayer = function(id, newScore) { scoreboardItem.children[0].classList.add("d"+tensDigit); scoreboardItem.children[1].classList.add("d"+onesDigit); } else { - logError("Could not find HTML scoreboard entry for "+this.players[id].toString()+"."); + logError("Could not find HTML scoreboard entry for player "+id+"."); + } + + // Results: + if (resultsItem instanceof HTMLElement && resultsItem.children[0] instanceof HTMLElement && resultsItem.children[1] instanceof HTMLElement) { + // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. + // First, we have to remove all digit classes: + resultsItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + resultsItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + // Add appropriate classes to tens and ones position, respectively: + resultsItem.children[0].classList.add("d"+tensDigit); + resultsItem.children[1].classList.add("d"+onesDigit); + } else { + logError("Could not find HTML results entry for player "+id+"."); } } }; From d3c75206fee6dad1dd6e9668d3c57ed9924fc069 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 9 Mar 2016 00:03:21 +0100 Subject: [PATCH 040/168] Solve scaling issues The scaling would not work as intended, e.g. when scaling lower than 640x480. When fixing this, I discovered that the #border4 > #border3 > #border2 > canvas (with border) structure severely hampered the ability to write good CSS. I have therefore added a div#border1 and removed all borders from the canvas. This commit also adds support for up to 10x scaling. --- zatacka.css | 155 ++++++++++++++++++++++++++++++--------------------- zatacka.html | 4 +- 2 files changed, 94 insertions(+), 65 deletions(-) diff --git a/zatacka.css b/zatacka.css index e44719a..9275c8e 100644 --- a/zatacka.css +++ b/zatacka.css @@ -47,12 +47,17 @@ body { .border { border-style: solid; - display: flex; + border-width: 1px; + display: flex; /* to prevent weird extra space at the bottom */ +} + +#border1 { + border-color: #828282; + position: relative; /* to allow absolute positioning of descendants*/ } #border2 { border-color: #717171; - position: relative; /* to allow absolute positioning of #lobby */ } #border3 { @@ -65,15 +70,14 @@ body { .overlay { display: block; - height: 480px; + height: 100%; left: 0; - margin: 1px; position: absolute; top: 0; - width: 559px; + width: 100%; } -#lobby, #KONEC_HRY { +#lobby, #KONEC_HRY, #canvas { background-color: black; } @@ -128,8 +132,6 @@ body { #lobby #controls .blue.ready { background-image: url("resources/kurve-lobby-ready-blue.png"); } #canvas { - background-color: black; - border-color: #828282; z-index: 10; } @@ -141,6 +143,8 @@ body { z-index: 5; box-sizing: border-box; min-height: 100%; + height: 480px; + width: 77px; } #scoreboard { @@ -234,24 +238,33 @@ body { #KONEC_HRY .digits .d9 { background-position: -252px -43px; } -/* ======== 1x ======== */ + + +/*************************/ +/******** SCALING ********/ +/*************************/ + /* -640 = Kurve width -480 = Kurve height -559 = Kurve field width (excluding border) -77 = Scoreboard width (excluding border) +640 = Kurve total width +480 = Kurve total height +559 = Kurve canvas width (excluding border) 81 = Scoreboard width (including border) 721 = 559 + 2*81 = total width including borders, #left and scoreboard */ + +@media screen and (max-width: 640px) { + #left { display: none; } + body { justify-content: flex-start; } + .border { border-left-width: 0; } +} + +/* ======== 1x ======== */ @media screen and (min-width: 640px) and (min-height: 480px) { body { justify-content: flex-end; } - #canvas { - width: 559px; - height: 480px; + #wrapper { + zoom: 1; + -moz-transform: scale(1); } - .border { border-width: 1px; } - aside { width: 77px; height: 480px; } - .digit { width: 28px; height: 43px; } } @media screen and (min-width: 721px) and (min-height: 480px) { @@ -280,7 +293,6 @@ body { zoom: 3; -moz-transform: scale(3); } - body { justify-content: center; } } @media screen and (min-width: 2163px) and (min-height: 1440px) { @@ -289,83 +301,98 @@ body { /* ======== 4x ======== */ -@media screen and (min-width: 2560px) and (min-height: 1920px) { /*TODO*/ +@media screen and (min-width: 2560px) and (min-height: 1920px) { body { justify-content: flex-end; } #wrapper { zoom: 4; + -moz-transform: scale(4); } } -@media screen and (min-width: 2884px) and (min-height: 1920px) { /*TODO*/ +@media screen and (min-width: 2884px) and (min-height: 1920px) { body { justify-content: center; } } /* ======== 5x ======== */ -@media screen and (min-width: 3200px) and (min-height: 2400px) { /*TODO*/ +@media screen and (min-width: 3200px) and (min-height: 2400px) { body { justify-content: flex-end; } #wrapper { zoom: 5; + -moz-transform: scale(5); } } -@media screen and (min-width: 3605px) and (min-height: 2400px) { /*TODO*/ +@media screen and (min-width: 3605px) and (min-height: 2400px) { body { justify-content: center; } } -/* ======== 2x ======== */ -/*@media screen and (min-width: 1280px) and (min-height: 960px) { +/* ======== 6x ======== */ +@media screen and (min-width: 3840px) and (min-height: 2880px) { body { justify-content: flex-end; } - #canvas { - width: 1118px; - height: 960px; + #wrapper { + zoom: 6; + -moz-transform: scale(6); } - .border { border-width: 2px; } - aside { width: 154px; height: 960px; } - #scoreboard { padding-left: 18px; } - .digit { width: 56px; height: 86px; background-size: 560px; } -}*/ +} +@media screen and (min-width: 4326px) and (min-height: 2880px) { + body { justify-content: center; } +} -/* ======== 3x ======== */ -/*@media screen and (min-width: 1920px) and (min-height: 1440px) { + +/* ======== 7x ======== */ +@media screen and (min-width: 4480px) and (min-height: 3360px) { body { justify-content: flex-end; } - #canvas { - width: 1677px; - height: 1440px; + #wrapper { + zoom: 7; + -moz-transform: scale(7); } - .border { border-width: 3px; } - aside { width: 231px; height: 1440px; } - #scoreboard { padding-left: 27px; } - .digit { width: 84px; height: 129px; background-size: 840px; } -}*/ +} +@media screen and (min-width: 5047px) and (min-height: 3360px) { + body { justify-content: center; } +} -/* ======== 4x ======== */ -/*@media screen and (min-width: 2560px) and (min-height: 1920px) { + +/* ======== 8x ======== */ +@media screen and (min-width: 5120px) and (min-height: 3840px) { body { justify-content: flex-end; } - #canvas { - width: 2236px; - height: 1920px; + #wrapper { + zoom: 8; + -moz-transform: scale(8); } - .border { border-width: 4px; } - aside { width: 308px; height: 1920px; } - #scoreboard { padding-left: 36px; } - .digit { width: 112px; height: 172px; background-size: 1120px; } -}*/ +} +@media screen and (min-width: 5768px) and (min-height: 3840px) { + body { justify-content: center; } +} -/* ======== 5x ======== */ -/*@media screen and (min-width: 3200px) and (min-height: 2400px) { +/* ======== 9x ======== */ +@media screen and (min-width: 5760px) and (min-height: 4320px) { + body { justify-content: flex-end; } + #wrapper { + zoom: 9; + -moz-transform: scale(9); + } +} + +@media screen and (min-width: 6489px) and (min-height: 4320px) { + body { justify-content: center; } +} + + +/* ======== 10x ======== */ +@media screen and (min-width: 6400px) and (min-height: 4800px) { body { justify-content: flex-end; } - #canvas { - width: 2795px; - height: 2400px; + #wrapper { + zoom: 10; + -moz-transform: scale(10); } - .border { border-width: 5px; } - aside { width: 385px; height: 2400px; } - #scoreboard { padding-left: 45px; } - .digit { width: 140px; height: 215px; background-size: 1400px; } -}*/ +} + +@media screen and (min-width: 7210px) and (min-height: 4800px) { + body { justify-content: center; } +} diff --git a/zatacka.html b/zatacka.html index 4ede353..9d5682b 100644 --- a/zatacka.html +++ b/zatacka.html @@ -9,6 +9,7 @@
+
  • @@ -66,7 +67,8 @@
- + +
From 1b4de4956cd425853d2f142f325baa35c7aa85ae Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 9 Mar 2016 01:01:44 +0100 Subject: [PATCH 041/168] Add user-select: none --- zatacka.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zatacka.css b/zatacka.css index 9275c8e..a79ad73 100644 --- a/zatacka.css +++ b/zatacka.css @@ -41,8 +41,11 @@ body { #debug_blue { color: #00A2CB; } #wrapper { - display: flex; align-items: center; + display: flex; + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; } .border { From dbbe4f3c031b7276f8a951e465f50edbaad578f9 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 9 Mar 2016 21:21:39 +0100 Subject: [PATCH 042/168] Fix major tickrate/draw issues It would sometimes happen that a player would die just before actually bumping into anything. The reason for this was that maxTicksBeforeDraw was incorrectly calculated. I suspect that the reason why this would occur pretty seldom (I'm taking a blind guess at a 4/64 change), and why the error would never be more than 1 Kuxel, is that the max allowed framerate (60 fps) was very close to the Kurve speed (64 Kx/s). This commit rethinks the update/draw loop such that the max number of ticks between draws is calculated as the tickrate over the speed, e.g. 600 Hz / 64 Kx/s = 9.375, rounded down to the closest integer, or 1 if <1. --- zatacka.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/zatacka.js b/zatacka.js index bb6d55b..2ccf25c 100644 --- a/zatacka.js +++ b/zatacka.js @@ -13,8 +13,7 @@ var pixels = new Array(canvasWidth*canvasHeight).fill(0); var KEY = Object.freeze({ BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, PAUSE: 19, CAPS_LOCK: 20, ESCAPE: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40, INSERT: 45, DELETE: 46, "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 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, LEFT_META: 91, RIGHT_META: 92, SELECT: 93, NUMPAD_0: 96, NUMPAD_1: 97, NUMPAD_2: 98, NUMPAD_3: 99, NUMPAD_4: 100, NUMPAD_5: 101, NUMPAD_6: 102, NUMPAD_7: 103, NUMPAD_8: 104, NUMPAD_9: 105, MULTIPLY: 106, ADD: 107, SUBTRACT: 109, DECIMAL: 110, DIVIDE: 111, F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117, F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123, NUM_LOCK: 144, SCROLL_LOCK: 145, SEMICOLON: 186, EQUALS: 187, COMMA: 188, DASH: 189, PERIOD: 190, FORWARD_SLASH: 191, GRAVE_ACCENT: 192, OPEN_BRACKET: 219, BACK_SLASH: 220, CLOSE_BRACKET: 221, SINGLE_QUOTE: 222 }); const config = Object.freeze({ - tickrate: 600, // Hz - drawrate: 60, // Hz + tickrate: 600, // Hz (absolute minimum: 30) kurveThickness: 3, minSpawnAngle: -Math.PI/2, maxSpawnAngle: Math.PI/2, @@ -40,8 +39,22 @@ var defaultPlayers = [ { name: "Blue" , color: "#00A2CB", keyL: null , keyR: null } ]; -var ticksSinceDraw = 0; -var maxTicksBeforeDraw = config.tickrate/config.drawrate; +var totalNumberOfTicks = 0; +var maxTicksBeforeDraw = Math.max(Math.floor(config.tickrate/config.speed), 1); + +// maxTicksBeforeDraw exists because we do not want to queue a large number of unnecessary draws. +// It is calculated like so: +// We cannot allow a Kurve to travel more than 1 Kuxel without a draw, since that gives us the bug +// where it looks like you die before actually hitting an obstacle. +// s = vt (where s is 1 Kuxel and v is known) +// t = s / v = 1 / v +// t is the number of seconds we can wait before we have to draw. +// t equals some number of ticks, n, times the time of one tick, 1 / f: +// t = n / f (where f is the tickrate and n is the sought maxTicksBeforeDraw) +// 1 / v = n / f +// n = f / v +// We then Math.floor() it so we can do modulo calculations with it and because integers are nice. + function init() { @@ -238,7 +251,7 @@ function update(delta) { for (var i = 0, len = game.livePlayers.length; i < len; i++) { game.livePlayers[i].update(delta); } - ticksSinceDraw++; + totalNumberOfTicks++; } /** @@ -575,7 +588,7 @@ Player.prototype.update = function(delta) { var theta = this.velocity * delta / 1000; this.x = this.x + theta * Math.cos(this.direction); this.y = this.y - theta * Math.sin(this.direction); - if (this.isAlive() && ticksSinceDraw % maxTicksBeforeDraw === 0) { + if (this.isAlive() && totalNumberOfTicks % maxTicksBeforeDraw === 0) { this.queuedDraws.enqueue({"x": this.x, "y": this.y }); } }; From bf0abff64037a3fc07ab5b8910364081c0ead667 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 9 Mar 2016 22:13:47 +0100 Subject: [PATCH 043/168] Force -pi/2 < direction <= pi/2 --- lib/utilities.js | 18 ++++++++++++++++++ zatacka.js | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/utilities.js b/lib/utilities.js index b7a2742..ebe2c02 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -11,6 +11,24 @@ function isInt(n) { return Object.typeOf(n) === 'number' && n % 1 === 0; } +function round(number, decimals) { + return Math.round(number * (Math.pow(10, decimals))) / (Math.pow(10, decimals)); +} + +function normalizeAngle(a) { + var pi = Math.PI; + var angle = a % (2*pi); + angle = (angle + 2*pi) % (2*pi); + if (angle > pi) { + angle -= 2*pi; + } + return angle; +} + +function radToDeg(r) { + return (180/Math.PI) * r; +} + function log(str) { console.log("Zatacka: " + str); } diff --git a/zatacka.js b/zatacka.js index 2ccf25c..d00c653 100644 --- a/zatacka.js +++ b/zatacka.js @@ -152,6 +152,7 @@ function computeSpawnArea(margin) { }; } +// Computes the angle change for one tick when turning, in radians: function computeAngleChange() { return config.speed / (config.tickrate * config.turningRadius); } @@ -578,10 +579,12 @@ Player.prototype.update = function(delta) { if (Keyboard.isDown(this.keyR)) { this.direction -= computeAngleChange(); } + // We want the direction to stay in the interval -pi < angle <= pi: + this.direction = normalizeAngle(this.direction); // Debugging: var debugFieldID = "debug_" + this.getName().toLowerCase(); var debugField = document.getElementById(debugFieldID); - debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y); + debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y)+", dir = "+round(radToDeg(this.direction), 3); this.lastX = this.x; this.lastY = this.y; From 2f5a0533029bb3c59cf000024f41ab980d40d0dc Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 9 Mar 2016 22:21:29 +0100 Subject: [PATCH 044/168] Fix angle change before start bug Up until now, it was possible to turn during the flicker phase of the spawn procedure. This meant that players could effectively start to the left. As of this commit, a player can do nothing until they are alive, i.e. until they have started. --- zatacka.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/zatacka.js b/zatacka.js index d00c653..1249782 100644 --- a/zatacka.js +++ b/zatacka.js @@ -365,6 +365,7 @@ Player.prototype.setKeybind = function(dir, key) { }; Player.prototype.reset = function() { + logWarning("Resetting "+this); this.alive = false; this.lastY = null; this.lastX = null; @@ -573,26 +574,28 @@ Player.prototype.draw = function() { * The amount of time since the last time the player was updated, in seconds. */ Player.prototype.update = function(delta) { - if (Keyboard.isDown(this.keyL)) { - this.direction += computeAngleChange(); - } - if (Keyboard.isDown(this.keyR)) { - this.direction -= computeAngleChange(); - } - // We want the direction to stay in the interval -pi < angle <= pi: - this.direction = normalizeAngle(this.direction); // Debugging: var debugFieldID = "debug_" + this.getName().toLowerCase(); var debugField = document.getElementById(debugFieldID); debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y)+", dir = "+round(radToDeg(this.direction), 3); - this.lastX = this.x; - this.lastY = this.y; - var theta = this.velocity * delta / 1000; - this.x = this.x + theta * Math.cos(this.direction); - this.y = this.y - theta * Math.sin(this.direction); - if (this.isAlive() && totalNumberOfTicks % maxTicksBeforeDraw === 0) { - this.queuedDraws.enqueue({"x": this.x, "y": this.y }); + if (this.isAlive()) { + if (Keyboard.isDown(this.keyL)) { + this.direction += computeAngleChange(); + } + if (Keyboard.isDown(this.keyR)) { + this.direction -= computeAngleChange(); + } + // We want the direction to stay in the interval -pi < angle <= pi: + this.direction = normalizeAngle(this.direction); + this.lastX = this.x; + this.lastY = this.y; + var theta = this.velocity * delta / 1000; + this.x = this.x + theta * Math.cos(this.direction); + this.y = this.y - theta * Math.sin(this.direction); + if (totalNumberOfTicks % maxTicksBeforeDraw === 0) { + this.queuedDraws.enqueue({"x": this.x, "y": this.y }); + } } }; @@ -797,7 +800,6 @@ Game.prototype.stopPlayers = function() { var self = this; for (var i = 0, len = this.activePlayers.length; i < len; i++) { self.activePlayers[i].stop(); - self.activePlayers[i].reset(); } }; From 1240d03710ac29ba7ec1e10db11387e8873e5557 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 9 Mar 2016 23:48:26 +0100 Subject: [PATCH 045/168] Remove undesirable logWarning("Resetting "+this) --- zatacka.js | 1 - 1 file changed, 1 deletion(-) diff --git a/zatacka.js b/zatacka.js index 1249782..ac7cfc7 100644 --- a/zatacka.js +++ b/zatacka.js @@ -365,7 +365,6 @@ Player.prototype.setKeybind = function(dir, key) { }; Player.prototype.reset = function() { - logWarning("Resetting "+this); this.alive = false; this.lastY = null; this.lastX = null; From 5eb935465924c9b14810bde5953b89d6097033cc Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Wed, 9 Mar 2016 23:49:30 +0100 Subject: [PATCH 046/168] Fix scoreboard vertical padding --- zatacka.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zatacka.css b/zatacka.css index a79ad73..0fd3892 100644 --- a/zatacka.css +++ b/zatacka.css @@ -151,7 +151,7 @@ body { } #scoreboard { - padding: 0 12px 0 9px; + padding: 20px 12px 0 9px; } #KONEC_HRY { From 20b2f66269dd33891f91b7f7933a3b60570ade47 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Thu, 24 Mar 2016 13:12:24 +0100 Subject: [PATCH 047/168] Add several functions to utilities.js --- lib/utilities.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/lib/utilities.js b/lib/utilities.js index ebe2c02..77e12e6 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -1,3 +1,5 @@ +"use strict"; + Object.typeOf = (function typeOf(global) { return function(obj) { if (obj === global) { @@ -7,14 +9,36 @@ Object.typeOf = (function typeOf(global) { }; })(this); +const KEY = Object.freeze({ BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, PAUSE: 19, CAPS_LOCK: 20, ESCAPE: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40, INSERT: 45, DELETE: 46, "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 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, LEFT_META: 91, RIGHT_META: 92, SELECT: 93, NUMPAD_0: 96, NUMPAD_1: 97, NUMPAD_2: 98, NUMPAD_3: 99, NUMPAD_4: 100, NUMPAD_5: 101, NUMPAD_6: 102, NUMPAD_7: 103, NUMPAD_8: 104, NUMPAD_9: 105, MULTIPLY: 106, ADD: 107, SUBTRACT: 109, DECIMAL: 110, DIVIDE: 111, F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117, F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123, NUM_LOCK: 144, SCROLL_LOCK: 145, SEMICOLON: 186, EQUALS: 187, COMMA: 188, DASH: 189, PERIOD: 190, FORWARD_SLASH: 191, GRAVE_ACCENT: 192, OPEN_BRACKET: 219, BACK_SLASH: 220, CLOSE_BRACKET: 221, SINGLE_QUOTE: 222 }); + function isInt(n) { return Object.typeOf(n) === 'number' && n % 1 === 0; } +function isPositiveInt(n) { + return isInt(n) && n > 0; +} + function round(number, decimals) { return Math.round(number * (Math.pow(10, decimals))) / (Math.pow(10, decimals)); } +function sameAbs(a, b) { + return Math.abs(a) === Math.abs(b); +} + +/** + * Generates a random float between min (inclusive) and max (exclusive). + * + * @param {Number} min + * Minimum value (inclusive). + * @param {Number} max + * Maximum value (exclusive). + */ +function randomFloat(min, max) { + return Math.random() * (max - min) + min; +} + function normalizeAngle(a) { var pi = Math.PI; var angle = a % (2*pi); @@ -40,3 +64,45 @@ function logWarning(str) { function logError(str) { console.error("Zatacka: " + str); } + +// Returns the number of items in an array that satisfy the given checker function, +// i.e. the number of x's in array for which checker(x) === true: +function numberOfMatching(array, checker) { + var n = 0; + for (var i = 0, len = array.length; i < len; i++) { + if (checker.call(this, array[i]) === true) { + n++; + } + } + return n; +} + +function demand(object, checker) { + if (checker.call(this, object) === true) { + return object; + } else { + return undefined; + } +} + +function byID(id) { + return document.getElementById(id); +} + +function isHTMLElement(elem) { + return elem instanceof HTMLElement; +} + +const Keyboard = { + pressed: {}, + isDown: function(keyCode) { + return this.pressed[keyCode]; + }, + onKeydown: function(event) { + this.pressed[event.keyCode] = true; + }, + onKeyup: function(event) { + delete this.pressed[event.keyCode]; + } +}; + From 87d63084eae48c45e123d7122cd6bf42bcb76a94 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Thu, 24 Mar 2016 13:13:56 +0100 Subject: [PATCH 048/168] Move scripts/* into lib/ --- {scripts => lib}/Queue.js | 0 {scripts => lib}/mainloop.min.js | 0 {scripts => lib}/mainloop.min.js.map | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {scripts => lib}/Queue.js (100%) rename {scripts => lib}/mainloop.min.js (100%) rename {scripts => lib}/mainloop.min.js.map (100%) diff --git a/scripts/Queue.js b/lib/Queue.js similarity index 100% rename from scripts/Queue.js rename to lib/Queue.js diff --git a/scripts/mainloop.min.js b/lib/mainloop.min.js similarity index 100% rename from scripts/mainloop.min.js rename to lib/mainloop.min.js diff --git a/scripts/mainloop.min.js.map b/lib/mainloop.min.js.map similarity index 100% rename from scripts/mainloop.min.js.map rename to lib/mainloop.min.js.map From 2aa51793d871d9ca334734e4e75cce2e8bc4e429 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Thu, 24 Mar 2016 13:17:26 +0100 Subject: [PATCH 049/168] Commence major rewrite (classes etc) As of this commit, the entire code is being rewritten. We now have the independent Game and Player classes, as well as a Renderer and a GUIController object. This commit also includes an attempt at replicating the collision detection of the original game. --- GUIController.js | 114 +++++ Game.js | 508 ++++++++++++++++++++++ Player.js | 172 ++++++++ Renderer.js | 22 + zatacka.html | 10 +- zatacka.js | 1049 ++++------------------------------------------ 6 files changed, 909 insertions(+), 966 deletions(-) create mode 100644 GUIController.js create mode 100644 Game.js create mode 100644 Player.js create mode 100644 Renderer.js diff --git a/GUIController.js b/GUIController.js new file mode 100644 index 0000000..7a463f2 --- /dev/null +++ b/GUIController.js @@ -0,0 +1,114 @@ +"use strict"; + +function GUIController(cfg) { + +const CLASS_ACTIVE = "active"; +const CLASS_HIDDEN = "hidden"; + +const config = cfg; +const lobby = byID("lobby"); +const controls = byID("controls"); +const scoreboard = byID("scoreboard"); +const results = byID("results"); +const konecHry = byID("KONEC_HRY"); + + + +// PRIVATE FUNCTIONS + +function hideLobby() { + log("Hiding lobby."); + lobby.classList.add(CLASS_HIDDEN); +} + +function showScoreOfPlayer(id) { + var index = id - 1; + if (scoreboard instanceof HTMLElement) { + var scoreboardEntry = scoreboard.children[index]; + if (scoreboardEntry instanceof HTMLElement) { + scoreboardEntry.classList.add("active"); + } + } + if (results instanceof HTMLElement) { + var resultsEntry = results.children[index]; + if (resultsEntry instanceof HTMLElement) { + resultsEntry.classList.add("active"); + } + } +} + + +// PUBLIC API + +function playerReady(id) { + var index = id - 1; + try { + controls.children[index].children[1].classList.add(CLASS_ACTIVE); + } catch (e) { + console.error(e); + } +} + +function playerUnready(id) { + var index = id - 1; + try { + controls.children[index].children[1].classList.remove(CLASS_ACTIVE); + } catch (e) { + console.error(e); + } +} + +function gameStarted() { + hideLobby(); +} + +function initScoreOfPlayer(id) { + updateScoreOfPlayer(id, 0); + showScoreOfPlayer(id); +} + +function updateScoreOfPlayer(id, newScore) { + if (!(scoreboard instanceof HTMLElement)) { + logError("Scoreboard HTML element could not be found."); + } else if (!(results instanceof HTMLElement)) { + logError("Results HTML element could not be found."); + } else { + let scoreboardItem = scoreboard.children[id-1]; // minus 1 necessary since player IDs are 1-indexed + let resultsItem = results.children[id-1]; // minus 1 necessary since player IDs are 1-indexed + let onesDigit = newScore % 10; // digit at the ones position (4 in 14) + let tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) + + // Scoreboard: + if (scoreboardItem instanceof HTMLElement && scoreboardItem.children[0] instanceof HTMLElement && scoreboardItem.children[1] instanceof HTMLElement) { + // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. + // First, we have to remove all digit classes: + scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + // Add appropriate classes to tens and ones position, respectively: + scoreboardItem.children[0].classList.add("d"+tensDigit); + scoreboardItem.children[1].classList.add("d"+onesDigit); + } else { + logError("Could not find HTML scoreboard entry for player "+id+"."); + } + + // Results: + if (resultsItem instanceof HTMLElement && resultsItem.children[0] instanceof HTMLElement && resultsItem.children[1] instanceof HTMLElement) { + resultsItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + resultsItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + resultsItem.children[0].classList.add("d"+tensDigit); + resultsItem.children[1].classList.add("d"+onesDigit); + } else { + logError("Could not find HTML results entry for player "+id+"."); + } + } +} + +return { + playerReady: playerReady, + playerUnready: playerUnready, + gameStarted: gameStarted, + initScoreOfPlayer: initScoreOfPlayer, + updateScoreOfPlayer: updateScoreOfPlayer +}; + +} \ No newline at end of file diff --git a/Game.js b/Game.js new file mode 100644 index 0000000..ee04f05 --- /dev/null +++ b/Game.js @@ -0,0 +1,508 @@ +"use strict"; + +class Game { + constructor(config, renderer, guiController) { + // Class variables: + this.constructor.PRACTICE = "practice"; + this.constructor.COMPETITIVE = "competitive"; + this.constructor.DEFAULT_MODE = this.constructor.PRACTICE; + this.constructor.DEFAULT_TARGET_SCORE = 10; + this.constructor.MAX_TARGET_SCORE = 1000; + this.constructor.KONEC_HRY = "KONEC HRY!"; + + if (renderer === undefined) { + throw new TypeError("Cannot create a Game with no renderer."); + } else if (!this.constructor.isRenderer(renderer)) { + throw new TypeError(`${renderer} is not a valid renderer.`); + } + + if (guiController === undefined) { + throw new TypeError("Cannot create a Game with no GUI controller."); + } else if (!this.constructor.isGUIController(guiController)) { + throw new TypeError(`${guiController} is not a valid GUI controller.`); + } + + // Instance variables: + this.config = config; + this.pixels = (new Array(this.config.width * this.config.height)).fill(-1); + this.players = []; + this.rounds = []; + this.renderer = renderer; + this.guiController = guiController; + //this.scoreboard = []; + this.mode = this.constructor.DEFAULT_MODE; + this.started = false; + this.live = false; + this.waitingForNextRound = false; // TODO? + this.waitingForKonecHry = false; // TODO? + this.totalNumberOfTicks = 0; + this.targetScore = null; + this.angleChangePerTick = this.computeAngleChange(); + this.initMainLoop(); + this.colors = ["red", "green", "white"]; + this.currentColor = 0; + } + + nextColor() { + let c = this.colors[this.currentColor]; + this.currentColor++; + if (this.currentColor >= this.colors.length) { + this.currentColor = 0; + } + return c; + } + + static isRenderer(obj) { + // TODO + return obj !== undefined; + } + + static isGUIController(obj) { + // TODO + return obj !== undefined; + } + + static calculateTargetScore(numberOfPlayers) { + // Default target score is (n-1) * 10 for n players: + return (numberOfPlayers - 1) * 10; + } + + edgeOfSquare(coordinate) { + return Math.round(coordinate - this.config.thickness/2); + } + + // Computes the angle change for one tick when turning, in radians: + computeAngleChange() { + return this.config.speed / (this.config.tickrate * this.config.turningRadius); + } + + computeSpawnArea() { + return { + x_min: this.config.spawnMargin, + y_min: this.config.spawnMargin, + x_max: this.config.width - this.config.spawnMargin, + y_max: this.config.height - this.config.spawnMargin + }; + } + + computeFrontCornerPixel(edge, dir) { + let t = this.config.thickness; + let cf = 100; + return (cf*edge + cf*(t-1)/2 + cf*dir*(t-1)/2) / cf; + } + + computeFrontEdgePixel(edge, dir_parallel, dir_perpendicular, i) { + let t = this.config.thickness; + return edge + Math.abs(dir_parallel)*(t-1)/2 + dir_parallel*(t-1)/2 + Math.abs(dir_perpendicular)*i; + } + + computeHitbox(player, left, top) { + let hitboxPixels = []; + let lastDraw = player.getLastDraw(); + let dir_horizontal = left - lastDraw.left; // positive => going right; negative => going left + let dir_vertical = top - lastDraw.top; // positive => going down; negative => going up + // console.log(`left, top = ${left}, ${top}`); + // console.log(`lastDraw.left, lastDraw.top = ${lastDraw.left}, ${lastDraw.top}`); + // console.log(`dir_horizontal = ${dir_horizontal}`); + // console.log(`dir_vertical = ${dir_vertical}`); + if (sameAbs(dir_horizontal, dir_vertical)) { + // "45 degree" draw + let frontPixel_left = this.computeFrontCornerPixel(left, dir_horizontal); + let frontPixel_top = this.computeFrontCornerPixel(top, dir_vertical); + hitboxPixels.push(this.pixelAddress(frontPixel_left, frontPixel_top)); + } else { + // "90 degree" draw + for (let i = 0; i < this.config.thickness; i++) { + let frontPixel_left = this.computeFrontEdgePixel(left, dir_horizontal, dir_vertical, i); + let frontPixel_top = this.computeFrontEdgePixel(top, dir_vertical, dir_horizontal, i); + hitboxPixels.push(this.pixelAddress(frontPixel_left, frontPixel_top)); + } + } + return hitboxPixels; + } + + randomSpawnPosition() { + let spawnArea = this.computeSpawnArea(); + return { + x: randomFloat(spawnArea.x_min, spawnArea.x_max), + y: randomFloat(spawnArea.y_min, spawnArea.y_max) + }; + } + + randomSpawnAngle() { + // TODO + return Math.PI/4; + } + + pixelAddress(x, y) { + return y*this.config.width + x; + } + + pixelAddressToCoordinates(addr) { + let x = addr % this.config.width; + let y = (addr - x) / this.config.width; + return "("+x+", "+y+")"; + } + + + // GETTERS + + getMode() { + return this.mode; + } + + getTargetScore() { + return this.targetScore; + } + + getNumberOfActivePlayers() { + return this.players.length; + } + + + // SETTERS + + setMode(m) { + if (m === this.constructor.COMPETITIVE || m === this.constructor.PRACTICE) { + log(`Setting game mode to ${m}.`); + this.mode = m; + } else { + logError(`${m} is not a valid game mode. Keeping ${this.getMode()}.`); + } + } + + setTargetScore(s) { + let ts = this.constructor.DEFAULT_TARGET_SCORE; + let mts = this.constructor.MAX_TARGET_SCORE; + // Neither floats nor negative numbers are allowed: + if (isInt(s) && s > 0) { + // Check if the desired target score is allowed: + if (s > mts) { + // It is too high. Fall back to max value: + logWarning(`${s} is larger than the maximum allowed target score of ${mts}. Falling back to ${mts}.`); + ts = mts; + } else { + // The desired target score is OK! + log(`Setting target score to ${s}.`); + ts = s; + } + } else { + logWarning(`${s} is not a valid target score. Defaulting to ${ts}.`); + } + this.targetScore = ts; + } + + + // CHECKERS + + isStarted() { + return this.started; + } + + isLive() { + return this.live; + } + + isOver() { + return this.gameOver; + } + + isCompetitive() { + return this.getMode() === this.constructor.COMPETITIVE; + } + + isOccupiedPixelAddress(addr) { + // TODO ????? + return this.pixels[addr] > 0; + } + + isCrashing(player, left, top) { + let hitboxPixels = this.computeHitbox(player, left, top); + for (let i = 0; i < hitboxPixels.length; i++) { + if (this.isOccupiedPixelAddress(hitboxPixels[i])) { + return true; + } + } + return false; + } + + /** + * Checks whether a draw at the specified coordinates is inside the field. + * @param {Number} left The x coordinate of the left edge of the draw. + * @param {Number} top The y coordinate of the top edge of the draw. + */ + isOnField(left, top) { + return left >= 0 + && top >= 0 + && left+this.config.thickness <= this.config.width + && top +this.config.thickness <= this.config.height; + } + + /** + * Checks whether there is a player with a specific ID in the game. + * @param {Number} id The ID to check for. + */ + hasPlayer(id) { + for (let i = 0; i < this.players.length; i++) { + if (this.players[i].getID() === id) { + return true; + } + } + } + + + // DOERS + + /** + * Adds a player to the game. + * @param {Player} player The player to add. + */ + addPlayer(player) { + if (Player.isPlayer(player)) { + if (!this.hasPlayer(player.getID())) { + log(`${player} ready!`); + this.players.push(player); + player.setGame(this); + this.GUI_playerReady(player.getID()); + } else { + logWarning(`Not adding ${player} to the game because there is already a player with ID ${player.getID()}.`); + } + } else { + throw new TypeError(`Cannot add ${player} to the game because it is not a player.`); + } + } + + /** + * Removes a player from the game. + * @param {Number} id The ID of the player to remove. + */ + removePlayer(id) { + for (let i = 0; i < this.players.length; i++) { + let player = this.players[i]; + if (player.getID() === id) { + log(`${player} unready!`); + this.players.splice(i, 1); + this.GUI_playerUnready(id); + } + } + } + + /** Starts the game. */ + start() { + if (this.isCompetitive()) { + this.setTargetScore(this.constructor.calculateTargetScore(this.getNumberOfActivePlayers())); + for (let i = 0; i < this.players.length; i++) { + let player = this.players[i]; + this.GUI_initScoreOfPlayer(player.getID()); + } + } + log("Starting game!"); + this.started = true; + this.GUI_gameStarted(); + MainLoop.start(); + this.nextRound(); + } + + /** Quits the game. */ + quit() { + window.reload(); + } + + /** Announce KONEC HRY, show results etc. */ + konecHry() { + log(this.constructor.KONEC_HRY); + } + + /** Proceeds to the next round */ + nextRound() { + // TODO + // Sort the players by their IDs so they spawn in the correct order: + this.sortPlayers(); + this.spawnAndStartPlayers(); + } + + endRound() { + this.live = false; + } + + sortPlayers() { + this.players.sort((a, b) => (a.getID() - b.getID())); + } + + /** Starts all players. */ + startPlayers() { + for (let i = 0; i < this.players.length; i++) { + this.players[i].start(); + } + this.live = true; + } + + occupy(player, left, top) { + player.occupy(left, top); + let right = left + this.config.thickness; + let bottom = top + this.config.thickness; + let id = player.getID(); + for (let y = top; y < bottom; y++) { + for (let x = left; x < right; x++) { + this.pixels[this.pixelAddress(x, y)] = id; + } + } + this.Render_drawSquare(left, top, player.getColor()); + } + + /** Spawns and then starts all players. */ + spawnAndStartPlayers() { + const self = this; + // Spawn each player, then wait for it to finish flickering before spawning the next one: + (function spawnPlayer(i) { + if (i < self.players.length) { + self.players[i].spawn(self.randomSpawnPosition(), self.randomSpawnAngle()); + setTimeout(() => { spawnPlayer(++i); }, self.config.flickerDuration); + } else { + // All players have spawned. Start them! + self.startPlayers(); + } + })(0); + } + + /** + * Draws a specific player. + */ + drawPlayer(player) { + const thickness = this.config.thickness; + while (player.isAlive() && !player.queuedDraws.isEmpty()) { + let currentDraw = player.queuedDraws.dequeue(); + let left = this.edgeOfSquare(currentDraw.x); + let top = this.edgeOfSquare(currentDraw.y); + if (!player.justDrewAt(left, top)) { + // The new draw position is not identical to the last one. + // TODO + let diff_left = left - player.getLastDraw().left; + let diff_top = top - player.getLastDraw().top; + if (!this.isOnField(left, top)) { + // The player wants to draw outside the playing field => DIE. + this.death(player, "crashed into the wall"); + } else if (this.isCrashing(player, left, top)) { + // The player wants to draw on a spot occupied by a Kurve => DIE. + this.death(player, "crashed"); + } else if (!player.isHoly()) { + // The player is allowed to draw and is not holy. + this.occupy(player, left, top); + } + } + } + } + + death(player, cause) { + // Increment score and update it TODO + // Check if round end + player.die(cause); + } + + keyHandler(pressedKey) { + if (this.waitingForNextRound) { + if (this.isProceedKey(pressedKey)) { + this.nextRound(); + } else if (this.isQuitKey(pressedKey)) { + this.quit(); + } + } else if (this.waitingForKonecHry) { + if (this.isProceedKey(pressedKey)) { + this.konecHry(); + } + } + } + + + // RENDERER AND GUI CONTROLLER COMMUNICATION + + GUI_playerReady(id) { + this.guiController.playerReady(id); + } + GUI_playerUnready(id) { + this.guiController.playerUnready(id); + } + GUI_initScoreOfPlayer(id) { + this.guiController.initScoreOfPlayer(id); + } + GUI_gameStarted() { + this.guiController.gameStarted(); + } + + Render_clearHeads() { + // TODO + } + Render_drawSquare(left, top, color) { + this.renderer.drawSquare(left, top, color, this.config.thickness); + } + Render_clearSquare(left, top) { + this.renderer.clearSquare(left, top, this.config.thickness); + } + + + // MAIN LOOP + + /** + * Updates everything on each tick. + * @param {Number} delta + * The amount of time since the last update, in seconds. + */ + update(delta) { + this.Render_clearHeads(); + for (let i = 0; i < this.players.length; i++) { + if (this.players[i].isAlive()) { + this.players[i].update(delta, this.totalNumberOfTicks); + } + } + this.totalNumberOfTicks++; + // Cycle players so the players take turns being prioritized: + if (this.isLive()) { + this.players.unshift(this.players.pop()); + } + } + + /** + * Draws all players. + */ + draw() { + for (let i = 0; i < this.players.length; i++) { + this.drawPlayer(this.players[i]); + } + } + + /** + * Updates the FPS counter etc. + * @param {Number} framerate + * The smoothed frames per second. + * @param {Boolean} panic + * Whether the main loop panicked because the simulation fell too far behind real time. + */ + end(framerate, panic) { + if (panic) { + let discardedTime = Math.round(MainLoop.resetFrameDelta()); + console.warn("Main loop panicked. Discarding " + discardedTime + "ms."); + } + } + + + /** + * Initiates the main loop. + */ + initMainLoop() { + this.MainLoop = MainLoop; + this.MainLoop + .setUpdate(this.update.bind(this)) + .setDraw(this.draw.bind(this)) + .setEnd(this.end.bind(this)) + .setSimulationTimestep(1000/this.config.tickrate) + .setMaxAllowedFPS(this.config.maxFramerate); + } + +} + + + + + + + diff --git a/Player.js b/Player.js new file mode 100644 index 0000000..2b06d20 --- /dev/null +++ b/Player.js @@ -0,0 +1,172 @@ +"use strict"; + +class Player { + constructor(id, name = "Player", color = "white", keyL = undefined, keyR = undefined) { + if (isPositiveInt(id)) { + this.id = id; + this.name = name; + this.color = color; + this.queuedDraws = new Queue(); + this.alive = false; + this.x = null; + this.y = null; + this.lastDraw = null; + this.direction = 0; + this.velocity = 0; + this.game = undefined; + this.config = undefined; + this.angleChange = undefined; + this.maxTicksBeforeDraw = undefined; + if (isPositiveInt(keyL)) { + this.keyL = keyL; + } else { + logWarning(`Creating player "${this.name}" without a LEFT key.`); + } + if (isPositiveInt(keyR)) { + this.keyR = keyR; + } else { + logWarning(`Creating player "${this.name}" without a RIGHT key.`); + } + } else { + throw new Error("Cannot create a player with ID "+id+"."); + } + } + + static isPlayer(p) { + return (p instanceof Player); + } + + + // CHECKERS + + isAlive() { + return this.alive; + } + + justDrewAt(left, top) { + return this.lastDraw.left === left && this.lastDraw.top === top; + } + + isHoly() { + return false; // TODO + } + + + // GETTERS + + getID() { + return this.id; + } + + getName() { + return this.name; + } + + getColor() { + return this.color; + } + + toString() { + return this.name; + } + + getLastDraw() { + return this.lastDraw; + } + + + // SETTERS + + setGame(game) { + this.game = game; + this.config = game.config; + this.angleChange = game.computeAngleChange(); + this.maxTicksBeforeDraw = Math.max(Math.floor(this.config.tickrate/this.config.speed), 1); + } + + + // DOERS + + flicker() { + let isVisible = false; + let left = this.game.edgeOfSquare(this.x); + let top = this.game.edgeOfSquare(this.y); + let color = this.getColor(); + this.flickerTicker = setInterval(() => { + if (isVisible) { + this.game.Render_clearSquare(left, top); + isVisible = false; + } else { + this.game.Render_drawSquare(left, top, color); + isVisible = true; + } + }, 1000/this.config.flickerFrequency); + } + + stopFlickering() { + clearInterval(this.flickerTicker); + let left = this.game.edgeOfSquare(this.x); + let top = this.game.edgeOfSquare(this.y); + this.game.Render_drawSquare(left, top, this.color); + } + + spawn(position, direction) { + if (!(this.game instanceof Game)) { + throw new TypeError(`${this} is not attached to any game.`); + } else { + log(`${this} spawning at (${round(position.x, 2)}, ${round(position.y, 2)}).`); + this.x = position.x; + this.y = position.y; + this.direction = direction; + this.flicker(); + let self = this; + setTimeout(() => { self.stopFlickering(); }, self.config.flickerDuration); + this.occupy(this.game.edgeOfSquare(this.x), this.game.edgeOfSquare(this.y)); + } + } + + start() { + log(`${this} starting.`); + this.alive = true; + this.velocity = this.config.speed; + } + + /** + * Called when the player does something that causes it do die. + * @param {String} cause The cause of death. + */ + die(cause) { + this.alive = false; + log(`${this} ${(cause || "died")} at (${round(this.x, 2)}, ${round(this.y, 2)}).` ); + } + + occupy(left, top) { + this.lastDraw = { + "left": left, + "top" : top + }; + } + + update(delta, totalNumberOfTicks) { + // Debugging: + let debugFieldID = "debug_" + this.getName().toLowerCase(); + let debugField = document.getElementById(debugFieldID); + debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y)+", dir = "+round(radToDeg(this.direction), 2); + if (this.isAlive()) { + if (Keyboard.isDown(this.keyL)) { + this.direction += this.angleChange; + } + if (Keyboard.isDown(this.keyR)) { + this.direction -= this.angleChange; + } + // We want the direction to stay in the interval -pi < dir <= pi: + this.direction = normalizeAngle(this.direction); + let theta = this.velocity * delta / 1000; + this.x = this.x + theta * Math.cos(this.direction); + this.y = this.y - theta * Math.sin(this.direction); + if (totalNumberOfTicks % this.maxTicksBeforeDraw === 0) { // TODO + this.queuedDraws.enqueue({"x": this.x, "y": this.y }); + } + } + } +} diff --git a/Renderer.js b/Renderer.js new file mode 100644 index 0000000..a8eb137 --- /dev/null +++ b/Renderer.js @@ -0,0 +1,22 @@ +"use strict"; + +function Renderer(cfg, canvas) { + +const config = cfg; +const context = canvas.getContext("2d"); + +function drawSquare(left, top, color, size) { + context.fillStyle = color; + context.fillRect(left, top, size, size); +} + +function clearSquare(left, top, size) { + context.clearRect(left, top, size, size); +} + +return { + drawSquare: drawSquare, + clearSquare: clearSquare +}; + +} \ No newline at end of file diff --git a/zatacka.html b/zatacka.html index 9d5682b..17d2ce1 100644 --- a/zatacka.html +++ b/zatacka.html @@ -109,8 +109,12 @@ - - + + - + + + + + diff --git a/zatacka.js b/zatacka.js index ac7cfc7..3e00974 100644 --- a/zatacka.js +++ b/zatacka.js @@ -1,1014 +1,137 @@ -// IIFE start -var Zatacka = (function(window, document) { "use strict"; -var canvas = document.getElementById("canvas"); -var context = canvas.getContext("2d"); -var heads = document.getElementById("heads"); -var headsContext = heads.getContext("2d"); -var canvasWidth = canvas.width; -var canvasHeight = canvas.height; -var pixels = new Array(canvasWidth*canvasHeight).fill(0); +const Zatacka = (function(window, document) { -var KEY = Object.freeze({ BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, PAUSE: 19, CAPS_LOCK: 20, ESCAPE: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40, INSERT: 45, DELETE: 46, "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 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, LEFT_META: 91, RIGHT_META: 92, SELECT: 93, NUMPAD_0: 96, NUMPAD_1: 97, NUMPAD_2: 98, NUMPAD_3: 99, NUMPAD_4: 100, NUMPAD_5: 101, NUMPAD_6: 102, NUMPAD_7: 103, NUMPAD_8: 104, NUMPAD_9: 105, MULTIPLY: 106, ADD: 107, SUBTRACT: 109, DECIMAL: 110, DIVIDE: 111, F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117, F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123, NUM_LOCK: 144, SCROLL_LOCK: 145, SEMICOLON: 186, EQUALS: 187, COMMA: 188, DASH: 189, PERIOD: 190, FORWARD_SLASH: 191, GRAVE_ACCENT: 192, OPEN_BRACKET: 219, BACK_SLASH: 220, CLOSE_BRACKET: 221, SINGLE_QUOTE: 222 }); +const canvas = byID("canvas"); const config = Object.freeze({ - tickrate: 600, // Hz (absolute minimum: 30) - kurveThickness: 3, - minSpawnAngle: -Math.PI/2, - maxSpawnAngle: Math.PI/2, - spawnMargin: 100, - maxPlayers: 6, - speed: 64, // Kuxels per second + tickrate: 600, // Hz + maxFramerate: 60, // Hz + width: 559, // Kuxels + height: 480, // Kuxels + thickness: 3, // Kuxels + speed: 60, // Kuxels per second turningRadius: 27, // Kuxels (NB: _radius_) + minSpawnAngle: -Math.PI/2, // radians + maxSpawnAngle: Math.PI/2, // radians + spawnMargin: 100, // Kuxels + maxPlayers: 6, flickerFrequency: 20, // Hz, when spawning flickerDuration: 830, // ms, when spawning minHoleDistance: 90, // Kuxels maxHoleDistance: 300, // Kuxels minHoleLength: 8, // Kuxels - maxHoleLength: 12 // Kuxels + maxHoleLength: 12, // Kuxels + keys: { + "proceed": [KEY.SPACE, KEY.ENTER], + "quit": [KEY.ESCAPE] + }, + defaultPlayers: Object.freeze([ + { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, + { id: 2, name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, + { id: 3, name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, + { id: 4, name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, + { id: 5, name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, + { id: 6, name: "Blue" , color: "#00A2CB", keyL: KEY.C , keyR: KEY.V } + ]) }); -var defaultPlayers = [ - null, // => very neat logic since Red = P1, Yellow = P2 etc - { name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, - { name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, - { name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, - { name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, - { name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, - { name: "Blue" , color: "#00A2CB", keyL: null , keyR: null } -]; - -var totalNumberOfTicks = 0; -var maxTicksBeforeDraw = Math.max(Math.floor(config.tickrate/config.speed), 1); - -// maxTicksBeforeDraw exists because we do not want to queue a large number of unnecessary draws. -// It is calculated like so: -// We cannot allow a Kurve to travel more than 1 Kuxel without a draw, since that gives us the bug -// where it looks like you die before actually hitting an obstacle. -// s = vt (where s is 1 Kuxel and v is known) -// t = s / v = 1 / v -// t is the number of seconds we can wait before we have to draw. -// t equals some number of ticks, n, times the time of one tick, 1 / f: -// t = n / f (where f is the tickrate and n is the sought maxTicksBeforeDraw) -// 1 / v = n / f -// n = f / v -// We then Math.floor() it so we can do modulo calculations with it and because integers are nice. - - -function init() { - +function isProceedKey(k) { + return config.keys.proceed.indexOf(k) !== -1; } +function isQuitKey(k) { + return config.keys.quit.indexOf(k) !== -1; +} -var Keyboard = { - pressed: {}, - isDown: function(keyCode) { - return this.pressed[keyCode]; - }, - onKeydown: function(event) { - this.pressed[event.keyCode] = true; - }, - onKeyup: function(event) { - delete this.pressed[event.keyCode]; - } -}; - -function isPlayerKey(keyCode) { - if (game.isStarted()) { - for (var i = 0; i < game.players.length; i++) { - if (game.players[i] instanceof Player && (keyCode === game.players[i].keyL || keyCode === game.players[i].keyR)) { - return true; - } - } - } else { - for (var i = 0; i < defaultPlayers.length; i++) { - if (defaultPlayers[i] !== null && (keyCode === defaultPlayers[i].keyL || keyCode === defaultPlayers[i].keyR)) { - return true; - } +function defaultPlayer(id) { + var dp; + for (var i = 0; i < config.defaultPlayers.length; i++) { + dp = config.defaultPlayers[i]; + if (dp.id === id) { + return new Player(dp.id, dp.name, dp.color, dp.keyL, dp.keyR); } } - return false; -} - -function isReservedKey(keyCode) { - // Explicitly disable Tab to prevent accidental unfocus: - return (keyCode === KEY.TAB || keyCode === KEY.SPACE || keyCode === KEY.ESCAPE || isPlayerKey(keyCode)); } -function keyDownHandler(event) { - Keyboard.onKeydown(event); - if (isReservedKey(event.keyCode)) { - event.preventDefault(); - } - if (game.isStarted()) { - game.inGameKeyHandler(event); - } else { - game.lobbyKeyHandler(event); +function proceedKeyPressedInLobby() { + var numberOfReadyPlayers = game.getNumberOfActivePlayers(); + if (numberOfReadyPlayers > 0) { + removeLobbyEventListeners(); + addGameEventListeners(); + game.setMode(numberOfReadyPlayers === 1 ? Game.PRACTICE : Game.COMPETITIVE); + game.start(); } } -function keyUpHandler(event) { - Keyboard.onKeyup(event); -} - -function isOnField(x, y) { - return x >= 0 - && y >= 0 - && x+config.kurveThickness <= canvasWidth - && y+config.kurveThickness <= canvasHeight; -} - -function isOccupiedPixel(x, y) { - return isOccupiedPixelAddress(pixelAddress(x, y)); -} - -function isOccupiedPixelAddress(addr) { - return pixels[addr] > 0; -} - -function randomHoleDistance() { - return Math.round(randomFloat(config.minHoleDistance, config.maxHoleDistance)); -} - -function randomHoleLength() { - return randomFloat(config.minHoleLength, config.maxHoleLength); -} - - - -/** - * Computes the available spawn area. - * - * @param {Number} margin - * Minimum distance to edge of game field. - */ -function computeSpawnArea(margin) { - return { - x_min: margin, - y_min: margin, - x_max: canvasWidth - margin, - y_max: canvasHeight - margin - }; -} - -// Computes the angle change for one tick when turning, in radians: -function computeAngleChange() { - return config.speed / (config.tickrate * config.turningRadius); -} - -/** - * Generates a random float between min (inclusive) and max (exclusive). - * - * @param {Number} min - * Minimum value (inclusive). - * @param {Number} max - * Maximum value (exclusive). - */ -function randomFloat(min, max) { - return Math.random() * (max - min) + min; -} - -// Translates a pair of coordinates (x, y) into a single pixel address: -function pixelAddress(x, y) { - return y*canvasWidth + x; -} - -function pixelAddressToCoordinates(addr) { - var x = addr % canvasWidth; - var y = (addr - x) / canvasWidth; - return "("+x+", "+y+")"; -} - -// Returns true iff the two specified rectangles overlap each other: -function isOverlap(left1, top1, left2, top2, thickness) { - return left2 > (left1 - thickness) - && left2 < (left1 + thickness) - && top2 > (top1 - thickness) - && top2 < (top1 + thickness); -} - -// Returns an array with the pixel addresses to the pixels comprising the square at (left, top): -function getPixels(left, top) { - var pixels = []; - var right = left + config.kurveThickness; - var bottom = top + config.kurveThickness; - for (var y = top; y < bottom; y++) { - for (var x = left; x < right; x++) { - pixels.push(pixelAddress(x, y)); +function keyPressedInLobby(pressedKey) { + for (var i = 0; i < config.defaultPlayers.length; i++) { + let player = config.defaultPlayers[i]; + if (pressedKey === player.keyL) { + game.addPlayer(defaultPlayer(player.id)); + } else if (pressedKey === player.keyR) { + game.removePlayer(player.id); } } - return pixels; -} - -function generateSpawnPosition() { - var spawnArea = computeSpawnArea(config.spawnMargin); - return { - x: randomFloat(spawnArea.x_min, spawnArea.x_max), - y: randomFloat(spawnArea.y_min, spawnArea.y_max) - }; } -function generateSpawnDirection() { - return randomFloat(config.minSpawnAngle, config.maxSpawnAngle); +function lobbyKeyHandler() { + let pressedKey = window.event.keyCode; + if (isProceedKey(pressedKey)) { + proceedKeyPressedInLobby(); + } else { + keyPressedInLobby(pressedKey); + } } -function drawKurveSquare(x, y, color) { - context.fillStyle = color; - context.fillRect(x, y, config.kurveThickness, config.kurveThickness); -} +function lobbyMouseHandler() { -function clearKurveSquare(x, y) { - context.clearRect(x, y, config.kurveThickness, config.kurveThickness); } -function clearHeads() { - headsContext.clearRect(0, 0, canvasWidth, canvasHeight); +function gameKeyHandler() { + game.keyHandler(window.event.keyCode); } +function gameMouseHandler() { -/** - * Draws all players. - * - * @param {Number} interpolationPercentage - * How much to interpolate between frames. - */ -function draw(interpolationPercentage) { - var livePlayers = game.livePlayers; - // We cannot cache the length here since it is changed if some player dies: - for (var i = 0; i < livePlayers.length; i++) { - livePlayers[i].draw(); - } } -/** - * Updates everything. - * - * @param {Number} delta - * The amount of time since the last update, in seconds. - */ -function update(delta) { - clearHeads(); - for (var i = 0, len = game.livePlayers.length; i < len; i++) { - game.livePlayers[i].update(delta); - } - totalNumberOfTicks++; +function addLobbyEventListeners() { + log("Adding lobby event listeners ..."); + document.addEventListener("keydown", lobbyKeyHandler); + document.addEventListener("mousedown", lobbyMouseHandler); + log("Done."); } -/** - * Updates the FPS counter etc. - * - * @param {Number} framerate - * The smoothed frames per second. - * @param {Boolean} panic - * Whether the main loop panicked because the simulation fell too far behind real time. - */ -function end(framerate, panic) { - if (panic) { - var discardedTime = Math.round(MainLoop.resetFrameDelta()); - console.warn("Main loop panicked. Discarding " + discardedTime + "ms."); - } +function removeLobbyEventListeners() { + log("Removing lobby event listeners ..."); + document.removeEventListener("keydown", lobbyKeyHandler); + document.removeEventListener("mousedown", lobbyMouseHandler); + log("Done."); } - - - - - -/** - * Player constructor - * - * @param {String} color - * The color of the player. - */ -function Player(id, name, color, keyL, keyR) { - if (isInt(id) && id > 0 && id <= config.maxPlayers) { - this.id = id; - this.name = name || defaultPlayers[id].name || "Player "+id; - this.color = color || defaultPlayers[id].color || "white"; - this.keyL = keyL || defaultPlayers[id].keyL || null; - this.keyR = keyR || defaultPlayers[id].keyR || null; - this.queuedDraws = new Queue(); - this.lastDraw = { "x": null, "y": null }; - this.secondLastDraw = { "x": null, "y": null }; - this.thirdLastDraw = { "x": null, "y": null }; - } else { - throw new Error("Cannot create a player with ID "+id+"."); - } +function addGameEventListeners() { + log("Adding game event listeners ..."); + document.addEventListener("keydown", Keyboard.onKeydown.bind(Keyboard)); + document.addEventListener("keyup", Keyboard.onKeyup.bind(Keyboard)); + document.addEventListener("keydown", gameKeyHandler); + document.addEventListener("mousedown", gameMouseHandler); + log("Done."); } -Player.DIR_L = "left"; -Player.DIR_R = "right"; - -Player.prototype.score = 0; -Player.prototype.alive = false; -Player.prototype.x = null; -Player.prototype.y = null; -Player.prototype.lastX = null; -Player.prototype.lastY = null; -Player.prototype.direction = 0; -Player.prototype.velocity = 0; -Player.prototype.keyL = null; -Player.prototype.keyR = null; -Player.prototype.flickerTicker = null; -Player.prototype.hole = false; -Player.prototype.holeTimer = null; - -Player.prototype.getID = function() { - return this.id; -}; - -Player.prototype.getName = function() { - return this.name; -}; - -Player.prototype.getScore = function() { - return this.score; -}; - -Player.prototype.toString = function() { - return this.name; -}; - -Player.prototype.isAlive = function() { - return this.alive; -}; - -Player.prototype.isHoly = function() { - return this.hole; -}; - -Player.prototype.occupies = function(left, top) { - var x, y; - var right = left + config.kurveThickness; - var bottom = top + config.kurveThickness; - for (y = top; y < bottom; y++) { - for (x = left; x < right; x++) { - if (pixels[pixelAddress(x, y)] > 0 && pixels[pixelAddress(x, y)] === this.id) { - return true; - } - } - } - return false; -}; - -Player.prototype.setKeybind = function(dir, key) { - if (dir === this.constructor.DIR_L) { - this.keyL = key; - log("Set LEFT key of "+this.toString()+" to "+key+"."); - } else if (dir === this.constructor.DIR_R) { - this.keyR = key; - log("Set RIGHT key of "+this.toString()+" to "+key+"."); - } else { - logWarning("Could not bind "+key+" to "+dir+" because it is not a valid direction."); - } -}; - -Player.prototype.reset = function() { - this.alive = false; - this.lastY = null; - this.lastX = null; - this.x = null; - this.y = null; - this.direction = 0; - this.queuedDraws = new Queue(); - this.lastDraw = { "x": null, "y": null }; - this.secondLastDraw = { "x": null, "y": null }; - this.thirdLastDraw = { "x": null, "y": null }; - this.hole = false; - clearTimeout(this.holeTimer); - this.holeTimer = null; -}; - -Player.prototype.flicker = function() { - var isVisible = false; - var x = Math.round(this.x - config.kurveThickness/2); - var y = Math.round(this.y - config.kurveThickness/2); - var color = this.color; - this.flickerTicker = setInterval(function() { - if (isVisible) { - clearKurveSquare(x, y); - isVisible = false; - } else { - drawKurveSquare(x, y, color); - isVisible = true; - } - }, 1000/config.flickerFrequency); -}; - -Player.prototype.stopFlickering = function() { - clearInterval(this.flickerTicker); - var x = Math.round(this.x - config.kurveThickness/2); - var y = Math.round(this.y - config.kurveThickness/2); - drawKurveSquare(x, y, this.color); -}; - -Player.prototype.spawn = function() { - var spawnPosition = generateSpawnPosition(); - this.x = spawnPosition.x; - this.y = spawnPosition.y; - var spawnDirection = generateSpawnDirection(); - this.direction = spawnDirection; - // Player should flicker when it spawns: - this.flicker(); - var self = this; - setTimeout(function() { self.stopFlickering(); }, config.flickerDuration); - log(this+" spawning at ("+Math.round(spawnPosition.x)+", "+Math.round(spawnPosition.y)+") with direction "+Math.round(spawnDirection*180/Math.PI)+" deg."); -}; - -// Even if the player should be going faster, the average -// *distance* between the holes will always be the same: -Player.prototype.randomHoleInterval = function() { - return randomHoleDistance() / this.velocity; -}; - -// So will the average hole size: -Player.prototype.randomHoleDuration = function() { - return randomHoleLength() / this.velocity; -}; - -Player.prototype.beginHole = function(self) { - if (self.isAlive()) { - self.hole = true; - var t = self.randomHoleDuration(); - self.holeTimer = setTimeout(self.endHole, t*1000, self); - } -}; - -Player.prototype.endHole = function(self) { - self.hole = false; - if (self.isAlive()) { - self.startHoleTimer(); - } -}; - -Player.prototype.startHoleTimer = function() { - var self = this; - var t = this.randomHoleInterval(); - log(new Date().getTime() % 100000+" | "+t+" seconds until "+this+"'s next hole."); // for debugging - self.holeTimer = setTimeout(self.beginHole, t*1000, self); -}; - -Player.prototype.start = function() { - this.alive = true; - this.velocity = config.speed; - this.startHoleTimer(); -}; - -Player.prototype.stop = function() { - this.alive = false; - this.velocity = 0; -}; - -Player.prototype.drawHead = function(left, top) { - headsContext.fillStyle = this.color; - headsContext.fillRect(left, top, config.kurveThickness, config.kurveThickness); -}; - -Player.prototype.occupy = function(left, top) { - // TODO thickness - var id = this.id; - var right = left + config.kurveThickness; - var bottom = top + config.kurveThickness; - var thickness = config.kurveThickness; - for (var y = top; y < bottom; y++) { - for (var x = left; x < right; x++) { - pixels[pixelAddress(x, y)] = id; - } - } - this.thirdLastDraw = { "x": this.secondLastDraw.x, "y": this.secondLastDraw.y }; - this.secondLastDraw = { "x": this.lastDraw.x, "y": this.lastDraw.y }; - this.lastDraw = { "x": left, "y": top }; - context.fillStyle = this.color; - context.fillRect(left, top, thickness, thickness); -}; - -Player.prototype.die = function(cause) { - log(this+" "+(cause || "died")+" at ("+Math.round(this.x)+", "+Math.round(this.y)+")."); - game.deathOf(this); - this.alive = false; -}; - -Player.prototype.incrementScore = function() { - this.score++; -}; - -Player.prototype.justDrewAt = function(left, top) { - return this.lastDraw.x === left && this.lastDraw.y === top; -}; - -Player.prototype.overlapsOwnNeck = function(left, top) { - return isOverlap(left, top, this.lastDraw.x, this.lastDraw.y, config.kurveThickness) - || isOverlap(left, top, this.secondLastDraw.x, this.secondLastDraw.y, config.kurveThickness) - || isOverlap(left, top, this.thirdLastDraw.x, this.thirdLastDraw.y, config.kurveThickness); -}; - -Player.prototype.getNewPixels = function(left, top) { - var right = left + config.kurveThickness; - var bottom = top + config.kurveThickness; - var newPixels = []; - var oldPixels = getPixels(this.lastDraw.x, this.lastDraw.y).concat(getPixels(this.secondLastDraw.x, this.secondLastDraw.y)).concat(getPixels(this.thirdLastDraw.x, this.thirdLastDraw.y)); - var maybeNewPixels = getPixels(left, top); - for (var i = 0, len = maybeNewPixels.length; i < len; i++) { - if (oldPixels.indexOf(maybeNewPixels[i]) === -1) { - newPixels.push(maybeNewPixels[i]); - } - } - return newPixels; -}; - -Player.prototype.isCrashing = function(left, top) { - var newPixels = this.getNewPixels(left, top); - var id = this.getID(); - for (var i = 0, len = newPixels.length; i < len; i++) { - if (isOccupiedPixelAddress(newPixels[i])) { - return true; - } - } - return false; -}; - -/** - * Draws the player. - * - * @param {Number} interpolationPercentage - * How much to interpolate between frames. - */ -Player.prototype.draw = function() { - var id = this.id; - var thickness = config.kurveThickness; - var currentDraw; - var left, top, right, bottom, x, y, pixelAddress; - while (this.isAlive() && !this.queuedDraws.isEmpty()) { - // Player is alive and there are queued draw operations to handle. - currentDraw = this.queuedDraws.dequeue(); - left = Math.round(currentDraw.x - thickness/2); - top = Math.round(currentDraw.y - thickness/2); - if (!this.justDrewAt(left, top)) { - // The new draw position is not identical to the last one. - if (!isOnField(left, top)) { - // The player wants to draw outside the playing field. - this.die("crashed into the wall"); - } else if (this.isCrashing(left, top)) { - // The player wants to draw on a spot occupied by itself or an opponent. - this.die("crashed"); - } else { - // Only draw body if not holy: - if (!this.isHoly()) { - this.occupy(left, top); - } - } - } - } - if (this.isAlive()) { - // Always draw the head of the Kurve, but only once per draw() call: - this.drawHead(left, top); - } -}; - -/** - * Updates the player's position. - * - * @param {Number} delta - * The amount of time since the last time the player was updated, in seconds. - */ -Player.prototype.update = function(delta) { - // Debugging: - var debugFieldID = "debug_" + this.getName().toLowerCase(); - var debugField = document.getElementById(debugFieldID); - debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y)+", dir = "+round(radToDeg(this.direction), 3); - - if (this.isAlive()) { - if (Keyboard.isDown(this.keyL)) { - this.direction += computeAngleChange(); - } - if (Keyboard.isDown(this.keyR)) { - this.direction -= computeAngleChange(); - } - // We want the direction to stay in the interval -pi < angle <= pi: - this.direction = normalizeAngle(this.direction); - this.lastX = this.x; - this.lastY = this.y; - var theta = this.velocity * delta / 1000; - this.x = this.x + theta * Math.cos(this.direction); - this.y = this.y - theta * Math.sin(this.direction); - if (totalNumberOfTicks % maxTicksBeforeDraw === 0) { - this.queuedDraws.enqueue({"x": this.x, "y": this.y }); - } - } -}; - - - - - - -/** - * Round constructor. A Round object holds information about a round. - */ -function Round(maxPlayers) { - this.scoreboard = (new Array(maxPlayers+1)).fill(0); -} - -Round.prototype.finished = false; -Round.prototype.length = null; - -Round.prototype.isFinished = function() { - return this.finished; -}; - -Round.prototype.getLength = function() { - return this.length; -}; - - - - - -/** - * Game constructor. A Game object holds information about a running game. - */ -function Game(maxPlayers) { - // Length not dependent on number of ACTIVE players; empty player slots are null: - this.players = new Array(maxPlayers+1).fill(null); - this.livePlayers = []; - this.activePlayers = []; - this.rounds = Game.emptyRoundsArray(maxPlayers); - this.scoreboard = (new Array(maxPlayers+1)).fill(null); - this.mode = undefined; - this.started = false; - this.waitingForNextRound = false; - this.waitingForKonecHry = false; - var self = this; - this.inGameKeyHandler = function(event) { - if (self.waitingForNextRound) { - if (event.keyCode === KEY.SPACE) { - self.nextRound(); - } else if (event.keyCode === KEY.ESCAPE) { - GUIController.escapePressedInGame(); - } - } else if (self.waitingForKonecHry) { - if (event.keyCode === KEY.SPACE) { - self.konecHry(); - } - } - }; +function removeGameEventListeners() { + log("Removing game event listeners ..."); + document.removeEventListener("keydown", gameKeyHandler); + document.removeEventListener("mousedown", gameMouseHandler); + log("Done."); } -Game.PRACTICE = "practice"; -Game.COMPETITIVE = "competitive"; - -Game.calculateTargetScore = function(numberOfActivePlayers) { - return (numberOfActivePlayers - 1) * 10; -}; - -Game.emptyRoundsArray = function(maxPlayers) { - var rounds = new Array(maxPlayers + 1); -}; - -Game.prototype.targetScore = null; - -Game.prototype.setMode = function(m) { - log("Setting game mode to "+m+"."); - this.mode = m; -}; - -Game.prototype.setTargetScore = function(s) { - log("Setting target score to "+s+"."); - this.targetScore = s; -}; - -Game.prototype.getMode = function() { - return this.mode; -}; - -Game.prototype.getTargetScore = function() { - return this.targetScore; -}; - -// Works in lobby: -Game.prototype.getNumberOfReadyPlayers = function() { - var n = 0; - for (var i = 0, len = this.players.length; i < len; i++) { - if (this.players[i] instanceof Player) { - n++; - } - } - return n; -}; - -// Works after game.start() only: -Game.prototype.getNumberOfActivePlayers = function() { - return this.activePlayers.length; -}; - -Game.prototype.isStarted = function() { - return this.started; -}; - -Game.prototype.lobbyKeyHandler = function(event) { - for (var i = 1; i < defaultPlayers.length; i++) { - if (event.keyCode === defaultPlayers[i].keyL) { - this.addPlayer(new Player(i)); - GUIController.playerReady(i); - } else if (event.keyCode === defaultPlayers[i].keyR) { - this.removePlayer(i); - GUIController.playerUnready(i); - } - } - if (event.keyCode === KEY.SPACE) { - var numberOfReadyPlayers = game.getNumberOfReadyPlayers(); - if (numberOfReadyPlayers > 0) { - game.setMode(numberOfReadyPlayers === 1 ? Game.PRACTICE : Game.COMPETITIVE); - GUIController.gameStarted(); - game.start(); - } - } -}; - - -/** - * Adds a player to the game. - * - * @param {Player} player - * The Player object representing the player. - */ -Game.prototype.addPlayer = function(player) { - var id = player.getID(); - if (this.players[id] === null) { - this.players[id] = player; - log("Player "+id+" ("+player+") ready!"); - } -}; - -/** - * Adds a player to the game. - * - * @param {Number} id - * The ID of the player to be removed. - */ -Game.prototype.removePlayer = function(id) { - if (this.players[id] instanceof Player) { - log("Player "+id+" ("+this.players[id]+") not ready."); - this.players[id] = null; - } -}; - -Game.prototype.start = function() { - this.started = true; - // Grab all ready players and put them in activePlayers: - for (var i = 0, len = this.players.length; i < len; i++) { - if (this.players[i] instanceof Player) { - // Tell GUI controller to show scores if in competitive: - if (this.mode === Game.COMPETITIVE) { - GUIController.updateScoreOfPlayer(i, 0); - GUIController.showScoreOfPlayer(i); - } - this.activePlayers.push(this.players[i]); - log("Added "+this.players[i]+" to activePlayers."); - } - } - var self = this; - this.setTargetScore(Game.calculateTargetScore(this.getNumberOfActivePlayers())); - this.nextRound(); -}; - -Game.prototype.spawnPlayers = function() { - var activePlayers = this.activePlayers; - var self = this; - function spawnNextPlayer(next) { - if (activePlayers[next] instanceof Player) { - activePlayers[next].spawn(); - setTimeout(spawnNextPlayer, config.flickerDuration, next+1); - } else { - self.startPlayers(); - } - } - spawnNextPlayer(0); -}; - -Game.prototype.startPlayers = function() { - var self = this; - for (var i = 0, len = this.activePlayers.length; i < len; i++) { - self.activePlayers[i].start(); - } - MainLoop.start(); -}; - -Game.prototype.stopPlayers = function() { - var self = this; - for (var i = 0, len = this.activePlayers.length; i < len; i++) { - self.activePlayers[i].stop(); - } -}; - -Game.prototype.clearField = function() { - context.clearRect(0, 0, canvasWidth, canvasHeight); - pixels.fill(0); -}; - -Game.prototype.nextRound = function() { - this.waitingForNextRound = false; - this.clearField(); - for (var i = 0; i < this.activePlayers.length; i++) { - this.livePlayers[i] = this.activePlayers[i]; - } - this.spawnPlayers(); -}; - -Game.prototype.endRound = function(winner) { - this.stopPlayers(); - log("Round over." + (winner instanceof Player ? " "+winner+" won." : "")); - var someoneWonTheGame = false; - for (var i = 0; i < this.activePlayers.length; i++) { - if (this.getMode() === Game.COMPETITIVE && this.activePlayers[i].getScore() >= this.getTargetScore()) { - someoneWonTheGame = true; - } - this.activePlayers[i].reset(); - } - if (someoneWonTheGame) { - this.waitingForKonecHry = true; - } else { - this.waitingForNextRound = true; - } -}; - -Game.prototype.quit = function() { - // TODO not perfect, but works for now: - window.location.reload(); -}; - -// Game over: -Game.prototype.konecHry = function() { - GUIController.showKonecHry(); -}; - -Game.prototype.deathOf = function(player) { - for (var i = 0; i < this.livePlayers.length; i++) { - if (this.livePlayers[i] === player) { - // Remove dead player from livePlayers: - this.livePlayers.splice(i, 1); - break; - } - } - for (var i = 0, len = this.livePlayers.length; i < len; i++) { - this.livePlayers[i].incrementScore(); - GUIController.updateScoreOfPlayer(this.livePlayers[i].getID(), this.livePlayers[i].getScore()); - } - if (this.livePlayers.length === 1 && this.getMode() === Game.COMPETITIVE) { - // livePlayers[0] won the round: - this.endRound(this.livePlayers[0]); - } else if (this.livePlayers.length === 0) { - // The round is over since everyone is dead: - this.endRound(); - } -}; - - - -var GUIController = {}; - -GUIController.lobby = document.getElementById("lobby"); -GUIController.controlsList = document.getElementById("controls"); -GUIController.scoreboard = document.getElementById("scoreboard"); -GUIController.results = document.getElementById("results"); -GUIController.konecHry = document.getElementById("KONEC_HRY"); - -GUIController.initLobby = function() { - log("======== Zatacka Lobby ========"); -}; - -GUIController.gameStarted = function() { - log("Hiding lobby."); - // Hide lobby: - this.lobby.classList.add("hidden"); -}; - -GUIController.showScoreOfPlayer = function(id) { - var index = id - 1; - var scoreboard = this.scoreboard; - if (scoreboard instanceof HTMLElement) { - var scoreboardEntry = scoreboard.children[index]; - if (scoreboardEntry instanceof HTMLElement) { - scoreboardEntry.classList.add("active"); - } - } - if (results instanceof HTMLElement) { - var resultsEntry = results.children[index]; - if (resultsEntry instanceof HTMLElement) { - resultsEntry.classList.add("active"); - } - } -}; - -GUIController.playerReady = function(id) { - var index = id - 1; - this.controlsList.children[index].children[1].classList.add("active"); -}; - -GUIController.playerUnready = function(id) { - var index = id - 1; - this.controlsList.children[index].children[1].classList.remove("active"); -}; - -/** - * Updates the displayed score of the specified player to the specified value. - * - * @param {Number} id - * The id of the player whose score is to be updated. - * @param {Number} newScore - * The new score to display. - */ -GUIController.updateScoreOfPlayer = function(id, newScore) { - if (!(this.scoreboard instanceof HTMLElement)) { - logError("Scoreboard HTML element could not be found."); - } else { - var scoreboardItem = this.scoreboard.children[id-1]; // minus 1 necessary since players are 1-indexed - var resultsItem = this.results.children[id-1]; // minus 1 necessary since players are 1-indexed - var onesDigit = newScore % 10; // digit at the ones position (4 in 14) - var tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) - - // Scoreboard: - if (scoreboardItem instanceof HTMLElement && scoreboardItem.children[0] instanceof HTMLElement && scoreboardItem.children[1] instanceof HTMLElement) { - // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. - // First, we have to remove all digit classes: - scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - // Add appropriate classes to tens and ones position, respectively: - scoreboardItem.children[0].classList.add("d"+tensDigit); - scoreboardItem.children[1].classList.add("d"+onesDigit); - } else { - logError("Could not find HTML scoreboard entry for player "+id+"."); - } - - // Results: - if (resultsItem instanceof HTMLElement && resultsItem.children[0] instanceof HTMLElement && resultsItem.children[1] instanceof HTMLElement) { - // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. - // First, we have to remove all digit classes: - resultsItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - resultsItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - // Add appropriate classes to tens and ones position, respectively: - resultsItem.children[0].classList.add("d"+tensDigit); - resultsItem.children[1].classList.add("d"+onesDigit); - } else { - logError("Could not find HTML results entry for player "+id+"."); - } - } -}; - -GUIController.resetScoreboard = function() { - if (!(this.scoreboard instanceof HTMLElement)) { - logError("Scoreboard HTML element could not be found."); - } else { - for (var i = 0; i < this.scoreboard.children.length; i++) { - scoreboard.children[i].classList.remove("active"); - } - } -}; - -GUIController.showKonecHry = function() { - this.konecHry.classList.remove("hidden"); - this.resetScoreboard(); -}; - -GUIController.escapePressedInGame = function() { - // TODO can be made better, but works for now: - if (confirm("Do you really wish to quit?")) { - game.quit(); - } -}; - -window.addEventListener("keydown", keyDownHandler, false); -window.addEventListener("keyup" , keyUpHandler , false); - -var game = new Game(config.maxPlayers); - -GUIController.initLobby(); - -// Debugging: -// game.players[1].direction = 0; -// game.players[1].x = 500; -// game.players[1].y = 50; -// game.players[2].direction = 180/Math.PI; -// game.players[2].x = 500; -// game.players[2].y = 100; - -function drawManually(x, y, color) { - context.fillStyle = color; - context.fillRect(x, y, config.kurveThickness, config.kurveThickness); -} +addLobbyEventListeners(); -MainLoop - .setUpdate(update) - .setDraw(draw) - .setEnd(end) - .setSimulationTimestep(1000/config.tickrate) - .setMaxAllowedFPS(60); +const game = new Game(config, Renderer(config, canvas), GUIController(config)); return { - "isOccupiedPixel": isOccupiedPixel, - "drawManually": drawManually + getConfig: function() { return config; } }; -// IIFE end })(window, document); From 5270b35991c761cf24b20a26c58c3b04d6c594bc Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 03:56:31 +0100 Subject: [PATCH 050/168] Use isHTMLElement instead of instanceof --- GUIController.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/GUIController.js b/GUIController.js index 7a463f2..b7d856f 100644 --- a/GUIController.js +++ b/GUIController.js @@ -23,15 +23,15 @@ function hideLobby() { function showScoreOfPlayer(id) { var index = id - 1; - if (scoreboard instanceof HTMLElement) { + if (isHTMLElement(scoreboard)) { var scoreboardEntry = scoreboard.children[index]; - if (scoreboardEntry instanceof HTMLElement) { + if (isHTMLElement(scoreboardEntry)) { scoreboardEntry.classList.add("active"); } } - if (results instanceof HTMLElement) { + if (isHTMLElement(results)) { var resultsEntry = results.children[index]; - if (resultsEntry instanceof HTMLElement) { + if (isHTMLElement(resultsEntry)) { resultsEntry.classList.add("active"); } } @@ -68,9 +68,9 @@ function initScoreOfPlayer(id) { } function updateScoreOfPlayer(id, newScore) { - if (!(scoreboard instanceof HTMLElement)) { + if (!isHTMLElement(scoreboard)) { logError("Scoreboard HTML element could not be found."); - } else if (!(results instanceof HTMLElement)) { + } else if (!isHTMLElement(results)) { logError("Results HTML element could not be found."); } else { let scoreboardItem = scoreboard.children[id-1]; // minus 1 necessary since player IDs are 1-indexed @@ -79,7 +79,7 @@ function updateScoreOfPlayer(id, newScore) { let tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) // Scoreboard: - if (scoreboardItem instanceof HTMLElement && scoreboardItem.children[0] instanceof HTMLElement && scoreboardItem.children[1] instanceof HTMLElement) { + if (isHTMLElement(scoreboardItem) && isHTMLElement(scoreboardItem.children[0]) && isHTMLElement(scoreboardItem.children[1])) { // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. // First, we have to remove all digit classes: scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); @@ -92,7 +92,7 @@ function updateScoreOfPlayer(id, newScore) { } // Results: - if (resultsItem instanceof HTMLElement && resultsItem.children[0] instanceof HTMLElement && resultsItem.children[1] instanceof HTMLElement) { + if (isHTMLElement(resultsItem) && isHTMLElement(resultsItem.children[0]) && isHTMLElement(resultsItem.children[1])) { resultsItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); resultsItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); resultsItem.children[0].classList.add("d"+tensDigit); From 9cda378762f792300acbbf756d6b5dd7f0482687 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 04:03:51 +0100 Subject: [PATCH 051/168] Explicit head + body and lang --- zatacka.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zatacka.html b/zatacka.html index 17d2ce1..e60fac2 100644 --- a/zatacka.html +++ b/zatacka.html @@ -1,9 +1,16 @@ -Achtung, die Kurve! + + + +Achtung, die Kurve! + + + +
@@ -118,3 +125,6 @@ + + + From 51059199f4b6e4c3375d685fd0d2fe7ac16dd460 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 05:19:52 +0100 Subject: [PATCH 052/168] Fix transform + flex bug transform does not affect layout, so for example scale(2) in combination with justify-content: flex-end would result in everything floating outside of the viewport (to the right). This was fixed by setting transform-origin: right, so that #wrapper is scaled "towards the left". However, this is not desired when the field should be centered, so we also had to set transform-origin: center in those cases. --- zatacka.css | 70 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/zatacka.css b/zatacka.css index 0fd3892..49164aa 100644 --- a/zatacka.css +++ b/zatacka.css @@ -265,13 +265,16 @@ body { @media screen and (min-width: 640px) and (min-height: 480px) { body { justify-content: flex-end; } #wrapper { - zoom: 1; - -moz-transform: scale(1); + transform: scale(1); + transform-origin: right; } } @media screen and (min-width: 721px) and (min-height: 480px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -279,13 +282,16 @@ body { @media screen and (min-width: 1280px) and (min-height: 960px) { body { justify-content: flex-end; } #wrapper { - zoom: 2; - -moz-transform: scale(2); + transform: scale(2); + transform-origin: right; } } @media screen and (min-width: 1442px) and (min-height: 960px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -293,13 +299,16 @@ body { @media screen and (min-width: 1920px) and (min-height: 1440px) { body { justify-content: flex-end; } #wrapper { - zoom: 3; - -moz-transform: scale(3); + transform: scale(3); + transform-origin: right; } } @media screen and (min-width: 2163px) and (min-height: 1440px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -307,13 +316,16 @@ body { @media screen and (min-width: 2560px) and (min-height: 1920px) { body { justify-content: flex-end; } #wrapper { - zoom: 4; - -moz-transform: scale(4); + transform: scale(4); + transform-origin: right; } } @media screen and (min-width: 2884px) and (min-height: 1920px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -321,13 +333,16 @@ body { @media screen and (min-width: 3200px) and (min-height: 2400px) { body { justify-content: flex-end; } #wrapper { - zoom: 5; - -moz-transform: scale(5); + transform: scale(5); + transform-origin: right; } } @media screen and (min-width: 3605px) and (min-height: 2400px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -335,13 +350,16 @@ body { @media screen and (min-width: 3840px) and (min-height: 2880px) { body { justify-content: flex-end; } #wrapper { - zoom: 6; - -moz-transform: scale(6); + transform: scale(6); + transform-origin: right; } } @media screen and (min-width: 4326px) and (min-height: 2880px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -349,13 +367,16 @@ body { @media screen and (min-width: 4480px) and (min-height: 3360px) { body { justify-content: flex-end; } #wrapper { - zoom: 7; - -moz-transform: scale(7); + transform: scale(7); + transform-origin: right; } } @media screen and (min-width: 5047px) and (min-height: 3360px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -363,13 +384,16 @@ body { @media screen and (min-width: 5120px) and (min-height: 3840px) { body { justify-content: flex-end; } #wrapper { - zoom: 8; - -moz-transform: scale(8); + transform: scale(8); + transform-origin: right; } } @media screen and (min-width: 5768px) and (min-height: 3840px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -377,13 +401,16 @@ body { @media screen and (min-width: 5760px) and (min-height: 4320px) { body { justify-content: flex-end; } #wrapper { - zoom: 9; - -moz-transform: scale(9); + transform: scale(9); + transform-origin: right; } } @media screen and (min-width: 6489px) and (min-height: 4320px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } @@ -391,11 +418,14 @@ body { @media screen and (min-width: 6400px) and (min-height: 4800px) { body { justify-content: flex-end; } #wrapper { - zoom: 10; - -moz-transform: scale(10); + transform: scale(10); + transform-origin: right; } } @media screen and (min-width: 7210px) and (min-height: 4800px) { body { justify-content: center; } + #wrapper { + transform-origin: center; + } } From 234a74847ea309570a05566a3d62d836810b5a50 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 05:30:51 +0100 Subject: [PATCH 053/168] Remove typeOf from Object --- lib/utilities.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utilities.js b/lib/utilities.js index 77e12e6..5d32490 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -1,6 +1,6 @@ "use strict"; -Object.typeOf = (function typeOf(global) { +const typeOf = ((global) => { return function(obj) { if (obj === global) { return "global"; @@ -12,7 +12,7 @@ Object.typeOf = (function typeOf(global) { const KEY = Object.freeze({ BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, PAUSE: 19, CAPS_LOCK: 20, ESCAPE: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40, INSERT: 45, DELETE: 46, "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 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, LEFT_META: 91, RIGHT_META: 92, SELECT: 93, NUMPAD_0: 96, NUMPAD_1: 97, NUMPAD_2: 98, NUMPAD_3: 99, NUMPAD_4: 100, NUMPAD_5: 101, NUMPAD_6: 102, NUMPAD_7: 103, NUMPAD_8: 104, NUMPAD_9: 105, MULTIPLY: 106, ADD: 107, SUBTRACT: 109, DECIMAL: 110, DIVIDE: 111, F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117, F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123, NUM_LOCK: 144, SCROLL_LOCK: 145, SEMICOLON: 186, EQUALS: 187, COMMA: 188, DASH: 189, PERIOD: 190, FORWARD_SLASH: 191, GRAVE_ACCENT: 192, OPEN_BRACKET: 219, BACK_SLASH: 220, CLOSE_BRACKET: 221, SINGLE_QUOTE: 222 }); function isInt(n) { - return Object.typeOf(n) === 'number' && n % 1 === 0; + return typeOf(n) === 'number' && n % 1 === 0; } function isPositiveInt(n) { From 7ed451efb9ce407f7e50cf3ff0e95620d15a12a2 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 05:54:51 +0100 Subject: [PATCH 054/168] Remove redundant angleChangePerTick --- Game.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Game.js b/Game.js index ee04f05..ad048c7 100644 --- a/Game.js +++ b/Game.js @@ -37,7 +37,6 @@ class Game { this.waitingForKonecHry = false; // TODO? this.totalNumberOfTicks = 0; this.targetScore = null; - this.angleChangePerTick = this.computeAngleChange(); this.initMainLoop(); this.colors = ["red", "green", "white"]; this.currentColor = 0; From 5ed951234283547e93b72ce3773054d492c68f9f Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 06:12:55 +0100 Subject: [PATCH 055/168] Move validation to top of Player constructor --- Player.js | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/Player.js b/Player.js index 2b06d20..02fd972 100644 --- a/Player.js +++ b/Player.js @@ -2,33 +2,32 @@ class Player { constructor(id, name = "Player", color = "white", keyL = undefined, keyR = undefined) { - if (isPositiveInt(id)) { - this.id = id; - this.name = name; - this.color = color; - this.queuedDraws = new Queue(); - this.alive = false; - this.x = null; - this.y = null; - this.lastDraw = null; - this.direction = 0; - this.velocity = 0; - this.game = undefined; - this.config = undefined; - this.angleChange = undefined; - this.maxTicksBeforeDraw = undefined; - if (isPositiveInt(keyL)) { - this.keyL = keyL; - } else { - logWarning(`Creating player "${this.name}" without a LEFT key.`); - } - if (isPositiveInt(keyR)) { - this.keyR = keyR; - } else { - logWarning(`Creating player "${this.name}" without a RIGHT key.`); - } + if (!isPositiveInt(id)) { + throw new TypeError(`Cannot create a player with ID ${id}.`); + } + this.id = id; + this.name = name; + this.color = color; + this.queuedDraws = new Queue(); + this.alive = false; + this.x = null; + this.y = null; + this.lastDraw = null; + this.direction = 0; + this.velocity = 0; + this.game = undefined; + this.config = undefined; + this.angleChange = undefined; + this.maxTicksBeforeDraw = undefined; + if (isPositiveInt(keyL)) { + this.keyL = keyL; + } else { + logWarning(`Creating player "${this.name}" without a LEFT key.`); + } + if (isPositiveInt(keyR)) { + this.keyR = keyR; } else { - throw new Error("Cannot create a player with ID "+id+"."); + logWarning(`Creating player "${this.name}" without a RIGHT key.`); } } From 113c7bc270a129a2cb59c197c8c562b611261bbe Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 06:13:03 +0100 Subject: [PATCH 056/168] Remove config.maxPlayers (never used) --- zatacka.js | 1 - 1 file changed, 1 deletion(-) diff --git a/zatacka.js b/zatacka.js index 3e00974..efc4685 100644 --- a/zatacka.js +++ b/zatacka.js @@ -15,7 +15,6 @@ const config = Object.freeze({ minSpawnAngle: -Math.PI/2, // radians maxSpawnAngle: Math.PI/2, // radians spawnMargin: 100, // Kuxels - maxPlayers: 6, flickerFrequency: 20, // Hz, when spawning flickerDuration: 830, // ms, when spawning minHoleDistance: 90, // Kuxels From 2645b33521a931f6bfada0ffe0e012e1c487a7b9 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 06:25:56 +0100 Subject: [PATCH 057/168] Rename one-letter arguments --- GUIController.js | 8 ++++---- Game.js | 24 ++++++++++++------------ Player.js | 4 ++-- lib/utilities.js | 4 ++-- zatacka.js | 8 ++++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/GUIController.js b/GUIController.js index b7d856f..7cc5cf9 100644 --- a/GUIController.js +++ b/GUIController.js @@ -44,8 +44,8 @@ function playerReady(id) { var index = id - 1; try { controls.children[index].children[1].classList.add(CLASS_ACTIVE); - } catch (e) { - console.error(e); + } catch (error) { + console.error(error); } } @@ -53,8 +53,8 @@ function playerUnready(id) { var index = id - 1; try { controls.children[index].children[1].classList.remove(CLASS_ACTIVE); - } catch (e) { - console.error(e); + } catch (error) { + console.error(error); } } diff --git a/Game.js b/Game.js index ad048c7..72b3924 100644 --- a/Game.js +++ b/Game.js @@ -161,32 +161,32 @@ class Game { // SETTERS - setMode(m) { - if (m === this.constructor.COMPETITIVE || m === this.constructor.PRACTICE) { - log(`Setting game mode to ${m}.`); - this.mode = m; + setMode(mode) { + if (mode === this.constructor.COMPETITIVE || mode === this.constructor.PRACTICE) { + log(`Setting game mode to ${mode}.`); + this.mode = mode; } else { - logError(`${m} is not a valid game mode. Keeping ${this.getMode()}.`); + logError(`${mode} is not a valid game mode. Keeping ${this.getMode()}.`); } } - setTargetScore(s) { + setTargetScore(score) { let ts = this.constructor.DEFAULT_TARGET_SCORE; let mts = this.constructor.MAX_TARGET_SCORE; // Neither floats nor negative numbers are allowed: - if (isInt(s) && s > 0) { + if (isInt(score) && score > 0) { // Check if the desired target score is allowed: - if (s > mts) { + if (score > mts) { // It is too high. Fall back to max value: - logWarning(`${s} is larger than the maximum allowed target score of ${mts}. Falling back to ${mts}.`); + logWarning(`${score} is larger than the maximum allowed target score of ${mts}. Falling back to ${mts}.`); ts = mts; } else { // The desired target score is OK! - log(`Setting target score to ${s}.`); - ts = s; + log(`Setting target score to ${score}.`); + ts = score; } } else { - logWarning(`${s} is not a valid target score. Defaulting to ${ts}.`); + logWarning(`${score} is not a valid target score. Defaulting to ${ts}.`); } this.targetScore = ts; } diff --git a/Player.js b/Player.js index 02fd972..0d4f98b 100644 --- a/Player.js +++ b/Player.js @@ -31,8 +31,8 @@ class Player { } } - static isPlayer(p) { - return (p instanceof Player); + static isPlayer(player) { + return (player instanceof Player); } diff --git a/lib/utilities.js b/lib/utilities.js index 5d32490..4e24fa4 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -49,8 +49,8 @@ function normalizeAngle(a) { return angle; } -function radToDeg(r) { - return (180/Math.PI) * r; +function radToDeg(radians) { + return (180/Math.PI) * radians; } function log(str) { diff --git a/zatacka.js b/zatacka.js index efc4685..ae7133a 100644 --- a/zatacka.js +++ b/zatacka.js @@ -35,12 +35,12 @@ const config = Object.freeze({ ]) }); -function isProceedKey(k) { - return config.keys.proceed.indexOf(k) !== -1; +function isProceedKey(key) { + return config.keys.proceed.indexOf(key) !== -1; } -function isQuitKey(k) { - return config.keys.quit.indexOf(k) !== -1; +function isQuitKey(key) { + return config.keys.quit.indexOf(key) !== -1; } function defaultPlayer(id) { From 4be3a204da92bc610eb72f7cd7ef5055c55e7a9d Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 06:39:25 +0100 Subject: [PATCH 058/168] Fix top-level indentation --- GUIController.js | 190 ++++++++++++++++++++-------------------- Renderer.js | 26 +++--- zatacka.js | 224 +++++++++++++++++++++++------------------------ 3 files changed, 220 insertions(+), 220 deletions(-) diff --git a/GUIController.js b/GUIController.js index 7cc5cf9..60a6e98 100644 --- a/GUIController.js +++ b/GUIController.js @@ -2,113 +2,113 @@ function GUIController(cfg) { -const CLASS_ACTIVE = "active"; -const CLASS_HIDDEN = "hidden"; + const CLASS_ACTIVE = "active"; + const CLASS_HIDDEN = "hidden"; -const config = cfg; -const lobby = byID("lobby"); -const controls = byID("controls"); -const scoreboard = byID("scoreboard"); -const results = byID("results"); -const konecHry = byID("KONEC_HRY"); + const config = cfg; + const lobby = byID("lobby"); + const controls = byID("controls"); + const scoreboard = byID("scoreboard"); + const results = byID("results"); + const konecHry = byID("KONEC_HRY"); -// PRIVATE FUNCTIONS + // PRIVATE FUNCTIONS -function hideLobby() { - log("Hiding lobby."); - lobby.classList.add(CLASS_HIDDEN); -} + function hideLobby() { + log("Hiding lobby."); + lobby.classList.add(CLASS_HIDDEN); + } -function showScoreOfPlayer(id) { - var index = id - 1; - if (isHTMLElement(scoreboard)) { - var scoreboardEntry = scoreboard.children[index]; - if (isHTMLElement(scoreboardEntry)) { - scoreboardEntry.classList.add("active"); + function showScoreOfPlayer(id) { + var index = id - 1; + if (isHTMLElement(scoreboard)) { + var scoreboardEntry = scoreboard.children[index]; + if (isHTMLElement(scoreboardEntry)) { + scoreboardEntry.classList.add("active"); + } } - } - if (isHTMLElement(results)) { - var resultsEntry = results.children[index]; - if (isHTMLElement(resultsEntry)) { - resultsEntry.classList.add("active"); + if (isHTMLElement(results)) { + var resultsEntry = results.children[index]; + if (isHTMLElement(resultsEntry)) { + resultsEntry.classList.add("active"); + } } } -} - - -// PUBLIC API - -function playerReady(id) { - var index = id - 1; - try { - controls.children[index].children[1].classList.add(CLASS_ACTIVE); - } catch (error) { - console.error(error); - } -} - -function playerUnready(id) { - var index = id - 1; - try { - controls.children[index].children[1].classList.remove(CLASS_ACTIVE); - } catch (error) { - console.error(error); - } -} - -function gameStarted() { - hideLobby(); -} - -function initScoreOfPlayer(id) { - updateScoreOfPlayer(id, 0); - showScoreOfPlayer(id); -} - -function updateScoreOfPlayer(id, newScore) { - if (!isHTMLElement(scoreboard)) { - logError("Scoreboard HTML element could not be found."); - } else if (!isHTMLElement(results)) { - logError("Results HTML element could not be found."); - } else { - let scoreboardItem = scoreboard.children[id-1]; // minus 1 necessary since player IDs are 1-indexed - let resultsItem = results.children[id-1]; // minus 1 necessary since player IDs are 1-indexed - let onesDigit = newScore % 10; // digit at the ones position (4 in 14) - let tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) - - // Scoreboard: - if (isHTMLElement(scoreboardItem) && isHTMLElement(scoreboardItem.children[0]) && isHTMLElement(scoreboardItem.children[1])) { - // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. - // First, we have to remove all digit classes: - scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - // Add appropriate classes to tens and ones position, respectively: - scoreboardItem.children[0].classList.add("d"+tensDigit); - scoreboardItem.children[1].classList.add("d"+onesDigit); - } else { - logError("Could not find HTML scoreboard entry for player "+id+"."); - } - // Results: - if (isHTMLElement(resultsItem) && isHTMLElement(resultsItem.children[0]) && isHTMLElement(resultsItem.children[1])) { - resultsItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - resultsItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - resultsItem.children[0].classList.add("d"+tensDigit); - resultsItem.children[1].classList.add("d"+onesDigit); + + // PUBLIC API + + function playerReady(id) { + var index = id - 1; + try { + controls.children[index].children[1].classList.add(CLASS_ACTIVE); + } catch (error) { + console.error(error); + } + } + + function playerUnready(id) { + var index = id - 1; + try { + controls.children[index].children[1].classList.remove(CLASS_ACTIVE); + } catch (error) { + console.error(error); + } + } + + function gameStarted() { + hideLobby(); + } + + function initScoreOfPlayer(id) { + updateScoreOfPlayer(id, 0); + showScoreOfPlayer(id); + } + + function updateScoreOfPlayer(id, newScore) { + if (!isHTMLElement(scoreboard)) { + logError("Scoreboard HTML element could not be found."); + } else if (!isHTMLElement(results)) { + logError("Results HTML element could not be found."); } else { - logError("Could not find HTML results entry for player "+id+"."); + let scoreboardItem = scoreboard.children[id-1]; // minus 1 necessary since player IDs are 1-indexed + let resultsItem = results.children[id-1]; // minus 1 necessary since player IDs are 1-indexed + let onesDigit = newScore % 10; // digit at the ones position (4 in 14) + let tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) + + // Scoreboard: + if (isHTMLElement(scoreboardItem) && isHTMLElement(scoreboardItem.children[0]) && isHTMLElement(scoreboardItem.children[1])) { + // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. + // First, we have to remove all digit classes: + scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + // Add appropriate classes to tens and ones position, respectively: + scoreboardItem.children[0].classList.add("d"+tensDigit); + scoreboardItem.children[1].classList.add("d"+onesDigit); + } else { + logError("Could not find HTML scoreboard entry for player "+id+"."); + } + + // Results: + if (isHTMLElement(resultsItem) && isHTMLElement(resultsItem.children[0]) && isHTMLElement(resultsItem.children[1])) { + resultsItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + resultsItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); + resultsItem.children[0].classList.add("d"+tensDigit); + resultsItem.children[1].classList.add("d"+onesDigit); + } else { + logError("Could not find HTML results entry for player "+id+"."); + } } } -} - -return { - playerReady: playerReady, - playerUnready: playerUnready, - gameStarted: gameStarted, - initScoreOfPlayer: initScoreOfPlayer, - updateScoreOfPlayer: updateScoreOfPlayer -}; + + return { + playerReady: playerReady, + playerUnready: playerUnready, + gameStarted: gameStarted, + initScoreOfPlayer: initScoreOfPlayer, + updateScoreOfPlayer: updateScoreOfPlayer + }; } \ No newline at end of file diff --git a/Renderer.js b/Renderer.js index a8eb137..5f7221f 100644 --- a/Renderer.js +++ b/Renderer.js @@ -2,21 +2,21 @@ function Renderer(cfg, canvas) { -const config = cfg; -const context = canvas.getContext("2d"); + const config = cfg; + const context = canvas.getContext("2d"); -function drawSquare(left, top, color, size) { - context.fillStyle = color; - context.fillRect(left, top, size, size); -} + function drawSquare(left, top, color, size) { + context.fillStyle = color; + context.fillRect(left, top, size, size); + } -function clearSquare(left, top, size) { - context.clearRect(left, top, size, size); -} + function clearSquare(left, top, size) { + context.clearRect(left, top, size, size); + } -return { - drawSquare: drawSquare, - clearSquare: clearSquare -}; + return { + drawSquare: drawSquare, + clearSquare: clearSquare + }; } \ No newline at end of file diff --git a/zatacka.js b/zatacka.js index ae7133a..a83071a 100644 --- a/zatacka.js +++ b/zatacka.js @@ -2,135 +2,135 @@ const Zatacka = (function(window, document) { -const canvas = byID("canvas"); - -const config = Object.freeze({ - tickrate: 600, // Hz - maxFramerate: 60, // Hz - width: 559, // Kuxels - height: 480, // Kuxels - thickness: 3, // Kuxels - speed: 60, // Kuxels per second - turningRadius: 27, // Kuxels (NB: _radius_) - minSpawnAngle: -Math.PI/2, // radians - maxSpawnAngle: Math.PI/2, // radians - spawnMargin: 100, // Kuxels - flickerFrequency: 20, // Hz, when spawning - flickerDuration: 830, // ms, when spawning - minHoleDistance: 90, // Kuxels - maxHoleDistance: 300, // Kuxels - minHoleLength: 8, // Kuxels - maxHoleLength: 12, // Kuxels - keys: { - "proceed": [KEY.SPACE, KEY.ENTER], - "quit": [KEY.ESCAPE] - }, - defaultPlayers: Object.freeze([ - { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, - { id: 2, name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, - { id: 3, name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, - { id: 4, name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, - { id: 5, name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, - { id: 6, name: "Blue" , color: "#00A2CB", keyL: KEY.C , keyR: KEY.V } - ]) -}); - -function isProceedKey(key) { - return config.keys.proceed.indexOf(key) !== -1; -} - -function isQuitKey(key) { - return config.keys.quit.indexOf(key) !== -1; -} - -function defaultPlayer(id) { - var dp; - for (var i = 0; i < config.defaultPlayers.length; i++) { - dp = config.defaultPlayers[i]; - if (dp.id === id) { - return new Player(dp.id, dp.name, dp.color, dp.keyL, dp.keyR); + const canvas = byID("canvas"); + + const config = Object.freeze({ + tickrate: 600, // Hz + maxFramerate: 60, // Hz + width: 559, // Kuxels + height: 480, // Kuxels + thickness: 3, // Kuxels + speed: 60, // Kuxels per second + turningRadius: 27, // Kuxels (NB: _radius_) + minSpawnAngle: -Math.PI/2, // radians + maxSpawnAngle: Math.PI/2, // radians + spawnMargin: 100, // Kuxels + flickerFrequency: 20, // Hz, when spawning + flickerDuration: 830, // ms, when spawning + minHoleDistance: 90, // Kuxels + maxHoleDistance: 300, // Kuxels + minHoleLength: 8, // Kuxels + maxHoleLength: 12, // Kuxels + keys: { + "proceed": [KEY.SPACE, KEY.ENTER], + "quit": [KEY.ESCAPE] + }, + defaultPlayers: Object.freeze([ + { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, + { id: 2, name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, + { id: 3, name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, + { id: 4, name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, + { id: 5, name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, + { id: 6, name: "Blue" , color: "#00A2CB", keyL: KEY.C , keyR: KEY.V } + ]) + }); + + function isProceedKey(key) { + return config.keys.proceed.indexOf(key) !== -1; + } + + function isQuitKey(key) { + return config.keys.quit.indexOf(key) !== -1; + } + + function defaultPlayer(id) { + var dp; + for (var i = 0; i < config.defaultPlayers.length; i++) { + dp = config.defaultPlayers[i]; + if (dp.id === id) { + return new Player(dp.id, dp.name, dp.color, dp.keyL, dp.keyR); + } } } -} - -function proceedKeyPressedInLobby() { - var numberOfReadyPlayers = game.getNumberOfActivePlayers(); - if (numberOfReadyPlayers > 0) { - removeLobbyEventListeners(); - addGameEventListeners(); - game.setMode(numberOfReadyPlayers === 1 ? Game.PRACTICE : Game.COMPETITIVE); - game.start(); + + function proceedKeyPressedInLobby() { + var numberOfReadyPlayers = game.getNumberOfActivePlayers(); + if (numberOfReadyPlayers > 0) { + removeLobbyEventListeners(); + addGameEventListeners(); + game.setMode(numberOfReadyPlayers === 1 ? Game.PRACTICE : Game.COMPETITIVE); + game.start(); + } } -} - -function keyPressedInLobby(pressedKey) { - for (var i = 0; i < config.defaultPlayers.length; i++) { - let player = config.defaultPlayers[i]; - if (pressedKey === player.keyL) { - game.addPlayer(defaultPlayer(player.id)); - } else if (pressedKey === player.keyR) { - game.removePlayer(player.id); + + function keyPressedInLobby(pressedKey) { + for (var i = 0; i < config.defaultPlayers.length; i++) { + let player = config.defaultPlayers[i]; + if (pressedKey === player.keyL) { + game.addPlayer(defaultPlayer(player.id)); + } else if (pressedKey === player.keyR) { + game.removePlayer(player.id); + } } } -} - -function lobbyKeyHandler() { - let pressedKey = window.event.keyCode; - if (isProceedKey(pressedKey)) { - proceedKeyPressedInLobby(); - } else { - keyPressedInLobby(pressedKey); + + function lobbyKeyHandler() { + let pressedKey = window.event.keyCode; + if (isProceedKey(pressedKey)) { + proceedKeyPressedInLobby(); + } else { + keyPressedInLobby(pressedKey); + } } -} -function lobbyMouseHandler() { + function lobbyMouseHandler() { -} + } -function gameKeyHandler() { - game.keyHandler(window.event.keyCode); -} + function gameKeyHandler() { + game.keyHandler(window.event.keyCode); + } -function gameMouseHandler() { + function gameMouseHandler() { -} + } -function addLobbyEventListeners() { - log("Adding lobby event listeners ..."); - document.addEventListener("keydown", lobbyKeyHandler); - document.addEventListener("mousedown", lobbyMouseHandler); - log("Done."); -} + function addLobbyEventListeners() { + log("Adding lobby event listeners ..."); + document.addEventListener("keydown", lobbyKeyHandler); + document.addEventListener("mousedown", lobbyMouseHandler); + log("Done."); + } -function removeLobbyEventListeners() { - log("Removing lobby event listeners ..."); - document.removeEventListener("keydown", lobbyKeyHandler); - document.removeEventListener("mousedown", lobbyMouseHandler); - log("Done."); -} + function removeLobbyEventListeners() { + log("Removing lobby event listeners ..."); + document.removeEventListener("keydown", lobbyKeyHandler); + document.removeEventListener("mousedown", lobbyMouseHandler); + log("Done."); + } -function addGameEventListeners() { - log("Adding game event listeners ..."); - document.addEventListener("keydown", Keyboard.onKeydown.bind(Keyboard)); - document.addEventListener("keyup", Keyboard.onKeyup.bind(Keyboard)); - document.addEventListener("keydown", gameKeyHandler); - document.addEventListener("mousedown", gameMouseHandler); - log("Done."); -} + function addGameEventListeners() { + log("Adding game event listeners ..."); + document.addEventListener("keydown", Keyboard.onKeydown.bind(Keyboard)); + document.addEventListener("keyup", Keyboard.onKeyup.bind(Keyboard)); + document.addEventListener("keydown", gameKeyHandler); + document.addEventListener("mousedown", gameMouseHandler); + log("Done."); + } -function removeGameEventListeners() { - log("Removing game event listeners ..."); - document.removeEventListener("keydown", gameKeyHandler); - document.removeEventListener("mousedown", gameMouseHandler); - log("Done."); -} + function removeGameEventListeners() { + log("Removing game event listeners ..."); + document.removeEventListener("keydown", gameKeyHandler); + document.removeEventListener("mousedown", gameMouseHandler); + log("Done."); + } -addLobbyEventListeners(); + addLobbyEventListeners(); -const game = new Game(config, Renderer(config, canvas), GUIController(config)); + const game = new Game(config, Renderer(config, canvas), GUIController(config)); -return { - getConfig: function() { return config; } -}; + return { + getConfig: function() { return config; } + }; })(window, document); From 56b1182f7cbf58b2964af1f1b6cd119e289a20d4 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 06:43:25 +0100 Subject: [PATCH 059/168] Convert indentation to spaces --- Player.js | 278 +++++++++++++++++++++++------------------------ Renderer.js | 28 ++--- lib/utilities.js | 16 +-- 3 files changed, 161 insertions(+), 161 deletions(-) diff --git a/Player.js b/Player.js index 0d4f98b..1bbd656 100644 --- a/Player.js +++ b/Player.js @@ -1,90 +1,90 @@ "use strict"; class Player { - constructor(id, name = "Player", color = "white", keyL = undefined, keyR = undefined) { - if (!isPositiveInt(id)) { - throw new TypeError(`Cannot create a player with ID ${id}.`); - } - this.id = id; - this.name = name; - this.color = color; - this.queuedDraws = new Queue(); - this.alive = false; - this.x = null; - this.y = null; - this.lastDraw = null; - this.direction = 0; - this.velocity = 0; - this.game = undefined; - this.config = undefined; - this.angleChange = undefined; - this.maxTicksBeforeDraw = undefined; - if (isPositiveInt(keyL)) { - this.keyL = keyL; - } else { - logWarning(`Creating player "${this.name}" without a LEFT key.`); - } - if (isPositiveInt(keyR)) { - this.keyR = keyR; - } else { - logWarning(`Creating player "${this.name}" without a RIGHT key.`); - } - } - - static isPlayer(player) { - return (player instanceof Player); - } - - - // CHECKERS - - isAlive() { - return this.alive; - } - - justDrewAt(left, top) { - return this.lastDraw.left === left && this.lastDraw.top === top; - } - - isHoly() { - return false; // TODO - } - - - // GETTERS - - getID() { - return this.id; - } - - getName() { - return this.name; - } - - getColor() { - return this.color; - } - - toString() { - return this.name; - } - - getLastDraw() { - return this.lastDraw; - } - - - // SETTERS - - setGame(game) { - this.game = game; - this.config = game.config; - this.angleChange = game.computeAngleChange(); - this.maxTicksBeforeDraw = Math.max(Math.floor(this.config.tickrate/this.config.speed), 1); - } - - - // DOERS + constructor(id, name = "Player", color = "white", keyL = undefined, keyR = undefined) { + if (!isPositiveInt(id)) { + throw new TypeError(`Cannot create a player with ID ${id}.`); + } + this.id = id; + this.name = name; + this.color = color; + this.queuedDraws = new Queue(); + this.alive = false; + this.x = null; + this.y = null; + this.lastDraw = null; + this.direction = 0; + this.velocity = 0; + this.game = undefined; + this.config = undefined; + this.angleChange = undefined; + this.maxTicksBeforeDraw = undefined; + if (isPositiveInt(keyL)) { + this.keyL = keyL; + } else { + logWarning(`Creating player "${this.name}" without a LEFT key.`); + } + if (isPositiveInt(keyR)) { + this.keyR = keyR; + } else { + logWarning(`Creating player "${this.name}" without a RIGHT key.`); + } + } + + static isPlayer(player) { + return (player instanceof Player); + } + + + // CHECKERS + + isAlive() { + return this.alive; + } + + justDrewAt(left, top) { + return this.lastDraw.left === left && this.lastDraw.top === top; + } + + isHoly() { + return false; // TODO + } + + + // GETTERS + + getID() { + return this.id; + } + + getName() { + return this.name; + } + + getColor() { + return this.color; + } + + toString() { + return this.name; + } + + getLastDraw() { + return this.lastDraw; + } + + + // SETTERS + + setGame(game) { + this.game = game; + this.config = game.config; + this.angleChange = game.computeAngleChange(); + this.maxTicksBeforeDraw = Math.max(Math.floor(this.config.tickrate/this.config.speed), 1); + } + + + // DOERS flicker() { let isVisible = false; @@ -102,70 +102,70 @@ class Player { }, 1000/this.config.flickerFrequency); } - stopFlickering() { - clearInterval(this.flickerTicker); - let left = this.game.edgeOfSquare(this.x); - let top = this.game.edgeOfSquare(this.y); - this.game.Render_drawSquare(left, top, this.color); - } - - spawn(position, direction) { - if (!(this.game instanceof Game)) { - throw new TypeError(`${this} is not attached to any game.`); - } else { - log(`${this} spawning at (${round(position.x, 2)}, ${round(position.y, 2)}).`); - this.x = position.x; - this.y = position.y; - this.direction = direction; - this.flicker(); - let self = this; - setTimeout(() => { self.stopFlickering(); }, self.config.flickerDuration); - this.occupy(this.game.edgeOfSquare(this.x), this.game.edgeOfSquare(this.y)); - } - } - - start() { - log(`${this} starting.`); - this.alive = true; - this.velocity = this.config.speed; - } + stopFlickering() { + clearInterval(this.flickerTicker); + let left = this.game.edgeOfSquare(this.x); + let top = this.game.edgeOfSquare(this.y); + this.game.Render_drawSquare(left, top, this.color); + } + + spawn(position, direction) { + if (!(this.game instanceof Game)) { + throw new TypeError(`${this} is not attached to any game.`); + } else { + log(`${this} spawning at (${round(position.x, 2)}, ${round(position.y, 2)}).`); + this.x = position.x; + this.y = position.y; + this.direction = direction; + this.flicker(); + let self = this; + setTimeout(() => { self.stopFlickering(); }, self.config.flickerDuration); + this.occupy(this.game.edgeOfSquare(this.x), this.game.edgeOfSquare(this.y)); + } + } + + start() { + log(`${this} starting.`); + this.alive = true; + this.velocity = this.config.speed; + } /** * Called when the player does something that causes it do die. * @param {String} cause The cause of death. */ - die(cause) { - this.alive = false; - log(`${this} ${(cause || "died")} at (${round(this.x, 2)}, ${round(this.y, 2)}).` ); - } - - occupy(left, top) { - this.lastDraw = { - "left": left, - "top" : top - }; - } - - update(delta, totalNumberOfTicks) { + die(cause) { + this.alive = false; + log(`${this} ${(cause || "died")} at (${round(this.x, 2)}, ${round(this.y, 2)}).` ); + } + + occupy(left, top) { + this.lastDraw = { + "left": left, + "top" : top + }; + } + + update(delta, totalNumberOfTicks) { // Debugging: let debugFieldID = "debug_" + this.getName().toLowerCase(); let debugField = document.getElementById(debugFieldID); debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y)+", dir = "+round(radToDeg(this.direction), 2); if (this.isAlive()) { - if (Keyboard.isDown(this.keyL)) { - this.direction += this.angleChange; - } - if (Keyboard.isDown(this.keyR)) { - this.direction -= this.angleChange; - } - // We want the direction to stay in the interval -pi < dir <= pi: - this.direction = normalizeAngle(this.direction); - let theta = this.velocity * delta / 1000; - this.x = this.x + theta * Math.cos(this.direction); - this.y = this.y - theta * Math.sin(this.direction); - if (totalNumberOfTicks % this.maxTicksBeforeDraw === 0) { // TODO - this.queuedDraws.enqueue({"x": this.x, "y": this.y }); - } + if (Keyboard.isDown(this.keyL)) { + this.direction += this.angleChange; + } + if (Keyboard.isDown(this.keyR)) { + this.direction -= this.angleChange; + } + // We want the direction to stay in the interval -pi < dir <= pi: + this.direction = normalizeAngle(this.direction); + let theta = this.velocity * delta / 1000; + this.x = this.x + theta * Math.cos(this.direction); + this.y = this.y - theta * Math.sin(this.direction); + if (totalNumberOfTicks % this.maxTicksBeforeDraw === 0) { // TODO + this.queuedDraws.enqueue({"x": this.x, "y": this.y }); + } } - } + } } diff --git a/Renderer.js b/Renderer.js index 5f7221f..8aff6e6 100644 --- a/Renderer.js +++ b/Renderer.js @@ -2,21 +2,21 @@ function Renderer(cfg, canvas) { - const config = cfg; - const context = canvas.getContext("2d"); + const config = cfg; + const context = canvas.getContext("2d"); - function drawSquare(left, top, color, size) { - context.fillStyle = color; - context.fillRect(left, top, size, size); - } + function drawSquare(left, top, color, size) { + context.fillStyle = color; + context.fillRect(left, top, size, size); + } - function clearSquare(left, top, size) { - context.clearRect(left, top, size, size); - } + function clearSquare(left, top, size) { + context.clearRect(left, top, size, size); + } - return { - drawSquare: drawSquare, - clearSquare: clearSquare - }; + return { + drawSquare: drawSquare, + clearSquare: clearSquare + }; -} \ No newline at end of file +} diff --git a/lib/utilities.js b/lib/utilities.js index 4e24fa4..44957d7 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -40,17 +40,17 @@ function randomFloat(min, max) { } function normalizeAngle(a) { - var pi = Math.PI; - var angle = a % (2*pi); - angle = (angle + 2*pi) % (2*pi); - if (angle > pi) { - angle -= 2*pi; - } - return angle; + var pi = Math.PI; + var angle = a % (2*pi); + angle = (angle + 2*pi) % (2*pi); + if (angle > pi) { + angle -= 2*pi; + } + return angle; } function radToDeg(radians) { - return (180/Math.PI) * radians; + return (180/Math.PI) * radians; } function log(str) { From 45e9c4fde56abda880ecc20b2c41e290dd6ed29d Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 07:14:15 +0100 Subject: [PATCH 060/168] Use event as argument instead of window.event This bug would manifest itself as "window.event is undefined" in Firefox. Now doing it the standards-compliant way instead. --- zatacka.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zatacka.js b/zatacka.js index a83071a..af5f2e0 100644 --- a/zatacka.js +++ b/zatacka.js @@ -74,8 +74,8 @@ const Zatacka = (function(window, document) { } } - function lobbyKeyHandler() { - let pressedKey = window.event.keyCode; + function lobbyKeyHandler(event) { + let pressedKey = event.keyCode; if (isProceedKey(pressedKey)) { proceedKeyPressedInLobby(); } else { @@ -87,8 +87,8 @@ const Zatacka = (function(window, document) { } - function gameKeyHandler() { - game.keyHandler(window.event.keyCode); + function gameKeyHandler(event) { + game.keyHandler(event.keyCode); } function gameMouseHandler() { From 4e1805eb2a5bb8a4e0da75604623676411aa5a7a Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 07:16:36 +0100 Subject: [PATCH 061/168] Add meta charset tag --- zatacka.html | 1 + 1 file changed, 1 insertion(+) diff --git a/zatacka.html b/zatacka.html index e60fac2..605cc83 100644 --- a/zatacka.html +++ b/zatacka.html @@ -4,6 +4,7 @@ + Achtung, die Kurve! From 9c7107583af49f506da33b2b252af96dae83b316 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 07:26:08 +0100 Subject: [PATCH 062/168] Use :not(:last-child) instead of additional rule --- zatacka.css | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/zatacka.css b/zatacka.css index 49164aa..5f2f96e 100644 --- a/zatacka.css +++ b/zatacka.css @@ -179,16 +179,11 @@ body { float: left; } -/* Don't display 0: */ -.digits .d0 { +/* Don't display 0, except for at the ones position: */ +.digits .d0:not(:last-child) { display: none; } -/* Except for at the ones position: */ -.digits .d0:last-child { - display: inline-block; -} - .red.digits div { background-image: url("resources/kurve-digits-red.png"); } .yellow.digits div { background-image: url("resources/kurve-digits-yellow.png"); } .orange.digits div { background-image: url("resources/kurve-digits-orange.png"); } From ea27e4f08b773410f9198f220de52e5b4a6e96db Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 12:20:44 +0100 Subject: [PATCH 063/168] Add isString and flush --- lib/utilities.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/utilities.js b/lib/utilities.js index 44957d7..e0b3c91 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -19,6 +19,10 @@ function isPositiveInt(n) { return isInt(n) && n > 0; } +function isString(s) { + return typeOf(s) === "string"; +} + function round(number, decimals) { return Math.round(number * (Math.pow(10, decimals))) / (Math.pow(10, decimals)); } @@ -93,6 +97,14 @@ function isHTMLElement(elem) { return elem instanceof HTMLElement; } +function flush(node) { + if (isHTMLElement(node)) { + node.textContent = ""; + } else { + throw new TypeError(`${node} is not a DOM node.`); + } +} + const Keyboard = { pressed: {}, isDown: function(keyCode) { From cfd82c960cb00799c5abcf64bf8fba33c5a461d9 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 12:21:14 +0100 Subject: [PATCH 064/168] Refactor scoreboard thoroughly As of this commit, the scoreboard contains no digit elements; those are instead (re)created and added on every scoreboard update, which means that there is no limit to the number of digits that can be displayed. The update scoreboard code is also much more functional and DRY. --- GUIController.js | 53 +++++++++++++++++++----------------------- Game.js | 14 ++++++++++- Player.js | 9 ++++++++ zatacka.css | 5 ---- zatacka.html | 60 ++++++++++-------------------------------------- 5 files changed, 57 insertions(+), 84 deletions(-) diff --git a/GUIController.js b/GUIController.js index 60a6e98..422f938 100644 --- a/GUIController.js +++ b/GUIController.js @@ -67,42 +67,35 @@ function GUIController(cfg) { showScoreOfPlayer(id); } - function updateScoreOfPlayer(id, newScore) { - if (!isHTMLElement(scoreboard)) { - logError("Scoreboard HTML element could not be found."); - } else if (!isHTMLElement(results)) { - logError("Results HTML element could not be found."); + function updateBoard(board, id, newScore) { + if (!isHTMLElement(board)) { + logWarning(`Cannot update any entry in ${board} because it is not an HTML element.`); } else { - let scoreboardItem = scoreboard.children[id-1]; // minus 1 necessary since player IDs are 1-indexed - let resultsItem = results.children[id-1]; // minus 1 necessary since player IDs are 1-indexed - let onesDigit = newScore % 10; // digit at the ones position (4 in 14) - let tensDigit = (newScore - (newScore % 10)) / 10; // digit at the tens position (1 in 14) - - // Scoreboard: - if (isHTMLElement(scoreboardItem) && isHTMLElement(scoreboardItem.children[0]) && isHTMLElement(scoreboardItem.children[1])) { - // The digit elements are ordered such that children[0] is ones, children[1] is tens, and so on. - // First, we have to remove all digit classes: - scoreboardItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - scoreboardItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - // Add appropriate classes to tens and ones position, respectively: - scoreboardItem.children[0].classList.add("d"+tensDigit); - scoreboardItem.children[1].classList.add("d"+onesDigit); - } else { - logError("Could not find HTML scoreboard entry for player "+id+"."); - } - - // Results: - if (isHTMLElement(resultsItem) && isHTMLElement(resultsItem.children[0]) && isHTMLElement(resultsItem.children[1])) { - resultsItem.children[0].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - resultsItem.children[1].classList.remove("d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"); - resultsItem.children[0].classList.add("d"+tensDigit); - resultsItem.children[1].classList.add("d"+onesDigit); + const entry = board.children[id-1]; + if (!isHTMLElement(entry)) { + logWarning(`Cannot update score of player ${id} because ${entry} is not an HTML element.`); } else { - logError("Could not find HTML results entry for player "+id+"."); + // The entry is an HTML element; let's update it! + const digitClassFactory = digit => "d"+digit; + const createDigit = () => document.createElement("div"); + // Turn 528 into ["d5", "d2", "d8"]: + const newScoreDigitClasses = newScore.toString().split("").map(digitClassFactory); + // Remove everything from the entry element before we insert new digits: + flush(entry); + newScoreDigitClasses.forEach((digitClass, index) => { + let digitElement = createDigit(); // A completely clean element ... + digitElement.classList.add(newScoreDigitClasses[index]); // ... that now has a digit class. + entry.appendChild(digitElement); + }); } } } + function updateScoreOfPlayer(id, newScore) { + updateBoard(scoreboard, id, newScore); + updateBoard(results, id, newScore); + } + return { playerReady: playerReady, playerUnready: playerUnready, diff --git a/Game.js b/Game.js index 72b3924..00735e2 100644 --- a/Game.js +++ b/Game.js @@ -29,7 +29,6 @@ class Game { this.rounds = []; this.renderer = renderer; this.guiController = guiController; - //this.scoreboard = []; this.mode = this.constructor.DEFAULT_MODE; this.started = false; this.live = false; @@ -392,10 +391,20 @@ class Game { } } + updateScores() { + const isAlive = player => player.isAlive(); + const updateScore = (player) => { + player.incrementScore(); + this.GUI_updateScoreOfPlayer(player.getID(), player.getScore()); + } + this.players.filter(isAlive).forEach(updateScore); + } + death(player, cause) { // Increment score and update it TODO // Check if round end player.die(cause); + this.updateScores(); } keyHandler(pressedKey) { @@ -424,6 +433,9 @@ class Game { GUI_initScoreOfPlayer(id) { this.guiController.initScoreOfPlayer(id); } + GUI_updateScoreOfPlayer(id, newScore) { + this.guiController.updateScoreOfPlayer(id, newScore); + } GUI_gameStarted() { this.guiController.gameStarted(); } diff --git a/Player.js b/Player.js index 1bbd656..9ad6b79 100644 --- a/Player.js +++ b/Player.js @@ -16,6 +16,7 @@ class Player { this.direction = 0; this.velocity = 0; this.game = undefined; + this.score = 0; this.config = undefined; this.angleChange = undefined; this.maxTicksBeforeDraw = undefined; @@ -73,6 +74,10 @@ class Player { return this.lastDraw; } + getScore() { + return this.score; + } + // SETTERS @@ -130,6 +135,10 @@ class Player { this.velocity = this.config.speed; } + incrementScore() { + this.score++; + } + /** * Called when the player does something that causes it do die. * @param {String} cause The cause of death. diff --git a/zatacka.css b/zatacka.css index 5f2f96e..7cc3a58 100644 --- a/zatacka.css +++ b/zatacka.css @@ -179,11 +179,6 @@ body { float: left; } -/* Don't display 0, except for at the ones position: */ -.digits .d0:not(:last-child) { - display: none; -} - .red.digits div { background-image: url("resources/kurve-digits-red.png"); } .yellow.digits div { background-image: url("resources/kurve-digits-yellow.png"); } .orange.digits div { background-image: url("resources/kurve-digits-orange.png"); } diff --git a/zatacka.html b/zatacka.html index 605cc83..1069754 100644 --- a/zatacka.html +++ b/zatacka.html @@ -48,30 +48,12 @@
@@ -81,30 +63,12 @@
From b91777a802404a9aa133f498bd73c6b066f54e99 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 12:26:30 +0100 Subject: [PATCH 065/168] Remove numberOfMatching and demand (never used) --- lib/utilities.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/utilities.js b/lib/utilities.js index e0b3c91..a57f7da 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -69,26 +69,6 @@ function logError(str) { console.error("Zatacka: " + str); } -// Returns the number of items in an array that satisfy the given checker function, -// i.e. the number of x's in array for which checker(x) === true: -function numberOfMatching(array, checker) { - var n = 0; - for (var i = 0, len = array.length; i < len; i++) { - if (checker.call(this, array[i]) === true) { - n++; - } - } - return n; -} - -function demand(object, checker) { - if (checker.call(this, object) === true) { - return object; - } else { - return undefined; - } -} - function byID(id) { return document.getElementById(id); } From 69fb0fedd8902a084ee90f88cdeca4e311bdaf5f Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 15:17:00 +0100 Subject: [PATCH 066/168] Convert some random indentation tabs to spaces --- GUIController.js | 40 +++--- zatacka.css | 342 +++++++++++++++++++++++------------------------ zatacka.js | 16 +-- 3 files changed, 199 insertions(+), 199 deletions(-) diff --git a/GUIController.js b/GUIController.js index 422f938..c7e13c5 100644 --- a/GUIController.js +++ b/GUIController.js @@ -41,30 +41,30 @@ function GUIController(cfg) { // PUBLIC API function playerReady(id) { - var index = id - 1; - try { - controls.children[index].children[1].classList.add(CLASS_ACTIVE); - } catch (error) { - console.error(error); - } + var index = id - 1; + try { + controls.children[index].children[1].classList.add(CLASS_ACTIVE); + } catch (error) { + console.error(error); + } } function playerUnready(id) { - var index = id - 1; - try { - controls.children[index].children[1].classList.remove(CLASS_ACTIVE); - } catch (error) { - console.error(error); - } + var index = id - 1; + try { + controls.children[index].children[1].classList.remove(CLASS_ACTIVE); + } catch (error) { + console.error(error); + } } function gameStarted() { - hideLobby(); + hideLobby(); } function initScoreOfPlayer(id) { - updateScoreOfPlayer(id, 0); - showScoreOfPlayer(id); + updateScoreOfPlayer(id, 0); + showScoreOfPlayer(id); } function updateBoard(board, id, newScore) { @@ -97,11 +97,11 @@ function GUIController(cfg) { } return { - playerReady: playerReady, - playerUnready: playerUnready, - gameStarted: gameStarted, - initScoreOfPlayer: initScoreOfPlayer, - updateScoreOfPlayer: updateScoreOfPlayer + playerReady: playerReady, + playerUnready: playerUnready, + gameStarted: gameStarted, + initScoreOfPlayer: initScoreOfPlayer, + updateScoreOfPlayer: updateScoreOfPlayer }; } \ No newline at end of file diff --git a/zatacka.css b/zatacka.css index 7cc3a58..ac52841 100644 --- a/zatacka.css +++ b/zatacka.css @@ -1,36 +1,36 @@ * { - margin: 0; - padding: 0; - image-rendering: -moz-crisp-edges; - image-rendering: pixelated; + margin: 0; + padding: 0; + image-rendering: -moz-crisp-edges; + image-rendering: pixelated; } html, body { - width: 100%; - height: 100%; + width: 100%; + height: 100%; } body { - background-color: #3C3C3C; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; + background-color: #3C3C3C; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; } #debug { - background-color: black; - border: 1px white solid; - left: 0; - position: fixed; - top: 0; - z-index: 50; + background-color: black; + border: 1px white solid; + left: 0; + position: fixed; + top: 0; + z-index: 50; } #debug span { - font-family: monospace; - font-size: 16px; - display: block; + font-family: monospace; + font-size: 16px; + display: block; } #debug_red { color: #FF2800; } @@ -41,84 +41,84 @@ body { #debug_blue { color: #00A2CB; } #wrapper { - align-items: center; - display: flex; - -moz-user-select: none; - -webkit-user-select: none; - user-select: none; + align-items: center; + display: flex; + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; } .border { - border-style: solid; - border-width: 1px; - display: flex; /* to prevent weird extra space at the bottom */ + border-style: solid; + border-width: 1px; + display: flex; /* to prevent weird extra space at the bottom */ } #border1 { - border-color: #828282; - position: relative; /* to allow absolute positioning of descendants*/ + border-color: #828282; + position: relative; /* to allow absolute positioning of descendants*/ } #border2 { - border-color: #717171; + border-color: #717171; } #border3 { - border-color: #616161; + border-color: #616161; } #border4 { - border-color: #515151; + border-color: #515151; } .overlay { - display: block; - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; + display: block; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; } #lobby, #KONEC_HRY, #canvas { - background-color: black; + background-color: black; } #lobby { - z-index: 20; + z-index: 20; } #lobby.hidden, #KONEC_HRY.hidden { - display: none; + display: none; } #lobby #controls { - list-style-type: none; - margin-left: 81px; - margin-top: 50px; + list-style-type: none; + margin-left: 81px; + margin-top: 50px; } #lobby #controls li { - height: 50px; + height: 50px; } #lobby #controls li div { - float: left; - height: 100%; - background-repeat: no-repeat; + float: left; + height: 100%; + background-repeat: no-repeat; } #lobby #controls .controls { - width: 159px; + width: 159px; } #lobby #controls .ready { - display: none; - width: 80px; + display: none; + width: 80px; } #lobby #controls .ready.active { - display: block; + display: block; } #lobby #controls .red.controls { background-image: url("resources/kurve-lobby-controls-red.png"); } @@ -135,48 +135,48 @@ body { #lobby #controls .blue.ready { background-image: url("resources/kurve-lobby-ready-blue.png"); } #canvas { - z-index: 10; + z-index: 10; } #heads { - z-index: 12; + z-index: 12; } #left, #scoreboard { - z-index: 5; - box-sizing: border-box; - min-height: 100%; - height: 480px; - width: 77px; + z-index: 5; + box-sizing: border-box; + min-height: 100%; + height: 480px; + width: 77px; } #scoreboard { - padding: 20px 12px 0 9px; + padding: 20px 12px 0 9px; } #KONEC_HRY { - z-index: 30; - background-image: url("resources/kurve-konec-hry.png"); + z-index: 30; + background-image: url("resources/kurve-konec-hry.png"); } #KONEC_HRY #results { - list-style-type: none; - margin-top: 80px; - margin-left: 250px; + list-style-type: none; + margin-top: 80px; + margin-left: 250px; } /* General rules for digits: */ .digits { - visibility: hidden; + visibility: hidden; } .active.digits { - visibility: visible; + visibility: visible; } .digits > div { - display: inline-block; - float: left; + display: inline-block; + float: left; } .red.digits div { background-image: url("resources/kurve-digits-red.png"); } @@ -188,13 +188,13 @@ body { /* Scoreboard digits: */ #scoreboard .digits { - height: 43px; - margin-bottom: 37px; + height: 43px; + margin-bottom: 37px; } #scoreboard .digits > div { - height: 43px; - width: 28px; + height: 43px; + width: 28px; } #scoreboard .digits .d0 { background-position: 0px 0; } @@ -210,13 +210,13 @@ body { /* KONEC HRY digits: */ #KONEC_HRY .digits { - height: 14px; - margin-bottom: 26px; + height: 14px; + margin-bottom: 26px; } #KONEC_HRY .digits > div { - height: 14px; - width: 16px; + height: 14px; + width: 16px; } #KONEC_HRY .digits .d0 { background-position: 0px -43px; } @@ -246,176 +246,176 @@ body { */ @media screen and (max-width: 640px) { - #left { display: none; } - body { justify-content: flex-start; } - .border { border-left-width: 0; } + #left { display: none; } + body { justify-content: flex-start; } + .border { border-left-width: 0; } } /* ======== 1x ======== */ @media screen and (min-width: 640px) and (min-height: 480px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(1); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(1); + transform-origin: right; + } } @media screen and (min-width: 721px) and (min-height: 480px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 2x ======== */ @media screen and (min-width: 1280px) and (min-height: 960px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(2); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(2); + transform-origin: right; + } } @media screen and (min-width: 1442px) and (min-height: 960px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 3x ======== */ @media screen and (min-width: 1920px) and (min-height: 1440px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(3); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(3); + transform-origin: right; + } } @media screen and (min-width: 2163px) and (min-height: 1440px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 4x ======== */ @media screen and (min-width: 2560px) and (min-height: 1920px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(4); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(4); + transform-origin: right; + } } @media screen and (min-width: 2884px) and (min-height: 1920px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 5x ======== */ @media screen and (min-width: 3200px) and (min-height: 2400px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(5); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(5); + transform-origin: right; + } } @media screen and (min-width: 3605px) and (min-height: 2400px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 6x ======== */ @media screen and (min-width: 3840px) and (min-height: 2880px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(6); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(6); + transform-origin: right; + } } @media screen and (min-width: 4326px) and (min-height: 2880px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 7x ======== */ @media screen and (min-width: 4480px) and (min-height: 3360px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(7); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(7); + transform-origin: right; + } } @media screen and (min-width: 5047px) and (min-height: 3360px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 8x ======== */ @media screen and (min-width: 5120px) and (min-height: 3840px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(8); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(8); + transform-origin: right; + } } @media screen and (min-width: 5768px) and (min-height: 3840px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 9x ======== */ @media screen and (min-width: 5760px) and (min-height: 4320px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(9); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(9); + transform-origin: right; + } } @media screen and (min-width: 6489px) and (min-height: 4320px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } /* ======== 10x ======== */ @media screen and (min-width: 6400px) and (min-height: 4800px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(10); - transform-origin: right; - } + body { justify-content: flex-end; } + #wrapper { + transform: scale(10); + transform-origin: right; + } } @media screen and (min-width: 7210px) and (min-height: 4800px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } + body { justify-content: center; } + #wrapper { + transform-origin: center; + } } diff --git a/zatacka.js b/zatacka.js index af5f2e0..8e34250 100644 --- a/zatacka.js +++ b/zatacka.js @@ -26,13 +26,13 @@ const Zatacka = (function(window, document) { "quit": [KEY.ESCAPE] }, defaultPlayers: Object.freeze([ - { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, - { id: 2, name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, - { id: 3, name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, - { id: 4, name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, - { id: 5, name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, - { id: 6, name: "Blue" , color: "#00A2CB", keyL: KEY.C , keyR: KEY.V } - ]) + { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, + { id: 2, name: "Yellow", color: "#C3C300", keyL: KEY.CTRL , keyR: KEY.ALT }, + { id: 3, name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, + { id: 4, name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, + { id: 5, name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, + { id: 6, name: "Blue" , color: "#00A2CB", keyL: KEY.C , keyR: KEY.V } + ]) }); function isProceedKey(key) { @@ -130,7 +130,7 @@ const Zatacka = (function(window, document) { const game = new Game(config, Renderer(config, canvas), GUIController(config)); return { - getConfig: function() { return config; } + getConfig: function() { return config; } }; })(window, document); From ab0d5e64facc4a6faab502bc71ae638aecc62619 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 15:20:04 +0100 Subject: [PATCH 067/168] Remove redundant return object property names --- GUIController.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GUIController.js b/GUIController.js index c7e13c5..48bb4e1 100644 --- a/GUIController.js +++ b/GUIController.js @@ -97,11 +97,11 @@ function GUIController(cfg) { } return { - playerReady: playerReady, - playerUnready: playerUnready, - gameStarted: gameStarted, - initScoreOfPlayer: initScoreOfPlayer, - updateScoreOfPlayer: updateScoreOfPlayer + playerReady, + playerUnready, + gameStarted, + initScoreOfPlayer, + updateScoreOfPlayer }; } \ No newline at end of file From 458c50469fa125171f61bf5d9d68d35b83b70600 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 20:46:09 +0100 Subject: [PATCH 068/168] Fix scaling pixelmapping issue When in 2x mode, Chrome would display everything without any pixelmapping at all. The reason for this is that the width of #wrapper was 721px -- an odd number, more generally denoted by 2n+1. Scaling this by an integer factor k gives a scaled width of (2n+1)k. Because of transform-origin: center, this new width is always centered relative to the original width 2n+1. This means that on each side (left and right), there will be ((2n+1)k - (2n+1))/2 = (2nk + k - 2n - 1)/2 = nk + k/2 - n - 1/2 = n(k-1) + (k-1)/2 new pixels. If that is not an integer, we're in trouble! n(k-1) is always an integer, so that's fine, but (k-1)/2 is only an integer when k is odd! So we will never achieve pixelmapping for even scaling factors (e.g. k = 2). This horrible bug was fixed by making #left 76px wide so that #wrapper is instead 720px wide, which makes much more sense than 721px. No need to overcomplicate anything! --- zatacka.css | 128 +++++++--------------------------------------------- 1 file changed, 16 insertions(+), 112 deletions(-) diff --git a/zatacka.css b/zatacka.css index ac52841..229e9d8 100644 --- a/zatacka.css +++ b/zatacka.css @@ -147,13 +147,17 @@ body { box-sizing: border-box; min-height: 100%; height: 480px; - width: 77px; } #scoreboard { + width: 77px; padding: 20px 12px 0 9px; } +#left { + width: 76px; /* 1 less than #scoreboard so width of #wrapper is an even number */ +} + #KONEC_HRY { z-index: 30; background-image: url("resources/kurve-konec-hry.png"); @@ -241,29 +245,31 @@ body { 640 = Kurve total width 480 = Kurve total height 559 = Kurve canvas width (excluding border) -81 = Scoreboard width (including border) -721 = 559 + 2*81 = total width including borders, #left and scoreboard +77 = #scoreboard width (excluding border) +76 = #left width (excluding border) +720 = 76 + 4 + 559 + 4 + 77 = total width including borders, #left, and #scoreboard */ -@media screen and (max-width: 640px) { +@media screen and (max-width: 639px) { #left { display: none; } body { justify-content: flex-start; } .border { border-left-width: 0; } } + /* ======== 1x ======== */ @media screen and (min-width: 640px) and (min-height: 480px) { body { justify-content: flex-end; } #wrapper { transform: scale(1); - transform-origin: right; + transform-origin: right; /* so the game stays properly in the viewport when not centered */ } } -@media screen and (min-width: 721px) and (min-height: 480px) { +@media screen and (min-width: 720px) and (min-height: 480px) { body { justify-content: center; } #wrapper { - transform-origin: center; + transform-origin: center; /* so the game is centered when possible */ } } @@ -277,7 +283,7 @@ body { } } -@media screen and (min-width: 1442px) and (min-height: 960px) { +@media screen and (min-width: 1440px) and (min-height: 960px) { body { justify-content: center; } #wrapper { transform-origin: center; @@ -294,7 +300,7 @@ body { } } -@media screen and (min-width: 2163px) and (min-height: 1440px) { +@media screen and (min-width: 2160px) and (min-height: 1440px) { body { justify-content: center; } #wrapper { transform-origin: center; @@ -311,109 +317,7 @@ body { } } -@media screen and (min-width: 2884px) and (min-height: 1920px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } -} - - -/* ======== 5x ======== */ -@media screen and (min-width: 3200px) and (min-height: 2400px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(5); - transform-origin: right; - } -} - -@media screen and (min-width: 3605px) and (min-height: 2400px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } -} - - -/* ======== 6x ======== */ -@media screen and (min-width: 3840px) and (min-height: 2880px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(6); - transform-origin: right; - } -} - -@media screen and (min-width: 4326px) and (min-height: 2880px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } -} - - -/* ======== 7x ======== */ -@media screen and (min-width: 4480px) and (min-height: 3360px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(7); - transform-origin: right; - } -} - -@media screen and (min-width: 5047px) and (min-height: 3360px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } -} - - -/* ======== 8x ======== */ -@media screen and (min-width: 5120px) and (min-height: 3840px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(8); - transform-origin: right; - } -} - -@media screen and (min-width: 5768px) and (min-height: 3840px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } -} - - -/* ======== 9x ======== */ -@media screen and (min-width: 5760px) and (min-height: 4320px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(9); - transform-origin: right; - } -} - -@media screen and (min-width: 6489px) and (min-height: 4320px) { - body { justify-content: center; } - #wrapper { - transform-origin: center; - } -} - - -/* ======== 10x ======== */ -@media screen and (min-width: 6400px) and (min-height: 4800px) { - body { justify-content: flex-end; } - #wrapper { - transform: scale(10); - transform-origin: right; - } -} - -@media screen and (min-width: 7210px) and (min-height: 4800px) { +@media screen and (min-width: 2880px) and (min-height: 1920px) { body { justify-content: center; } #wrapper { transform-origin: center; From 7076aae0275b8cea11bdd6ff8a43a9901af09538 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 21:18:06 +0100 Subject: [PATCH 069/168] Make randomSpawnAngle random (was dummy) --- Game.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Game.js b/Game.js index 00735e2..45c361e 100644 --- a/Game.js +++ b/Game.js @@ -128,8 +128,7 @@ class Game { } randomSpawnAngle() { - // TODO - return Math.PI/4; + return randomFloat(this.config.minSpawnAngle, this.config.maxSpawnAngle); } pixelAddress(x, y) { From d2e59e9add02771909d8dbc88d3d89d32e7ca8d8 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 21:22:48 +0100 Subject: [PATCH 070/168] Read width and height from canvas (were hardcoded) --- zatacka.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zatacka.js b/zatacka.js index 8e34250..1019bbd 100644 --- a/zatacka.js +++ b/zatacka.js @@ -7,8 +7,8 @@ const Zatacka = (function(window, document) { const config = Object.freeze({ tickrate: 600, // Hz maxFramerate: 60, // Hz - width: 559, // Kuxels - height: 480, // Kuxels + width: canvas.width, // Kuxels + height: canvas.height, // Kuxels thickness: 3, // Kuxels speed: 60, // Kuxels per second turningRadius: 27, // Kuxels (NB: _radius_) From e851f5332d5816571a860c01ed1a00958ade40b2 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 21:41:10 +0100 Subject: [PATCH 071/168] Remove return object prop names in Renderer --- Renderer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Renderer.js b/Renderer.js index 8aff6e6..0931ee1 100644 --- a/Renderer.js +++ b/Renderer.js @@ -15,8 +15,8 @@ function Renderer(cfg, canvas) { } return { - drawSquare: drawSquare, - clearSquare: clearSquare + drawSquare, + clearSquare }; } From 3c3f75e427829ff86275e05f2bc6a3dac59b6a76 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 21:46:54 +0100 Subject: [PATCH 072/168] Remove initScoreOfPlayer As of this commit, the score of a player need not be explicitly initialized or set as active; it gets the .active class every time it is updated anyway. --- GUIController.js | 23 +---------------------- Game.js | 5 +---- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/GUIController.js b/GUIController.js index 48bb4e1..241b28a 100644 --- a/GUIController.js +++ b/GUIController.js @@ -21,22 +21,6 @@ function GUIController(cfg) { lobby.classList.add(CLASS_HIDDEN); } - function showScoreOfPlayer(id) { - var index = id - 1; - if (isHTMLElement(scoreboard)) { - var scoreboardEntry = scoreboard.children[index]; - if (isHTMLElement(scoreboardEntry)) { - scoreboardEntry.classList.add("active"); - } - } - if (isHTMLElement(results)) { - var resultsEntry = results.children[index]; - if (isHTMLElement(resultsEntry)) { - resultsEntry.classList.add("active"); - } - } - } - // PUBLIC API @@ -62,11 +46,6 @@ function GUIController(cfg) { hideLobby(); } - function initScoreOfPlayer(id) { - updateScoreOfPlayer(id, 0); - showScoreOfPlayer(id); - } - function updateBoard(board, id, newScore) { if (!isHTMLElement(board)) { logWarning(`Cannot update any entry in ${board} because it is not an HTML element.`); @@ -82,6 +61,7 @@ function GUIController(cfg) { const newScoreDigitClasses = newScore.toString().split("").map(digitClassFactory); // Remove everything from the entry element before we insert new digits: flush(entry); + entry.classList.add("active"); newScoreDigitClasses.forEach((digitClass, index) => { let digitElement = createDigit(); // A completely clean element ... digitElement.classList.add(newScoreDigitClasses[index]); // ... that now has a digit class. @@ -100,7 +80,6 @@ function GUIController(cfg) { playerReady, playerUnready, gameStarted, - initScoreOfPlayer, updateScoreOfPlayer }; diff --git a/Game.js b/Game.js index 45c361e..7036e36 100644 --- a/Game.js +++ b/Game.js @@ -290,7 +290,7 @@ class Game { this.setTargetScore(this.constructor.calculateTargetScore(this.getNumberOfActivePlayers())); for (let i = 0; i < this.players.length; i++) { let player = this.players[i]; - this.GUI_initScoreOfPlayer(player.getID()); + this.GUI_updateScoreOfPlayer(player.getID(), player.getScore()); } } log("Starting game!"); @@ -429,9 +429,6 @@ class Game { GUI_playerUnready(id) { this.guiController.playerUnready(id); } - GUI_initScoreOfPlayer(id) { - this.guiController.initScoreOfPlayer(id); - } GUI_updateScoreOfPlayer(id, newScore) { this.guiController.updateScoreOfPlayer(id, newScore); } From 786d5212eaf6abb43dd7fa86d625eebd12c83759 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 21:57:02 +0100 Subject: [PATCH 073/168] Replace var with let/const everywhere --- GUIController.js | 4 ++-- zatacka.js | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/GUIController.js b/GUIController.js index 241b28a..d3d9d24 100644 --- a/GUIController.js +++ b/GUIController.js @@ -25,7 +25,7 @@ function GUIController(cfg) { // PUBLIC API function playerReady(id) { - var index = id - 1; + const index = id - 1; try { controls.children[index].children[1].classList.add(CLASS_ACTIVE); } catch (error) { @@ -34,7 +34,7 @@ function GUIController(cfg) { } function playerUnready(id) { - var index = id - 1; + const index = id - 1; try { controls.children[index].children[1].classList.remove(CLASS_ACTIVE); } catch (error) { diff --git a/zatacka.js b/zatacka.js index 1019bbd..5f3f8dd 100644 --- a/zatacka.js +++ b/zatacka.js @@ -44,9 +44,8 @@ const Zatacka = (function(window, document) { } function defaultPlayer(id) { - var dp; - for (var i = 0; i < config.defaultPlayers.length; i++) { - dp = config.defaultPlayers[i]; + for (let i = 0; i < config.defaultPlayers.length; i++) { + let dp = config.defaultPlayers[i]; if (dp.id === id) { return new Player(dp.id, dp.name, dp.color, dp.keyL, dp.keyR); } @@ -54,7 +53,7 @@ const Zatacka = (function(window, document) { } function proceedKeyPressedInLobby() { - var numberOfReadyPlayers = game.getNumberOfActivePlayers(); + const numberOfReadyPlayers = game.getNumberOfActivePlayers(); if (numberOfReadyPlayers > 0) { removeLobbyEventListeners(); addGameEventListeners(); @@ -64,7 +63,7 @@ const Zatacka = (function(window, document) { } function keyPressedInLobby(pressedKey) { - for (var i = 0; i < config.defaultPlayers.length; i++) { + for (let i = 0; i < config.defaultPlayers.length; i++) { let player = config.defaultPlayers[i]; if (pressedKey === player.keyL) { game.addPlayer(defaultPlayer(player.id)); From 4175065ced8d94bd471acd5be466f828c17bc666 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 26 Mar 2016 22:18:27 +0100 Subject: [PATCH 074/168] Improve defaultPlayer function --- zatacka.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/zatacka.js b/zatacka.js index 5f3f8dd..e6d18cd 100644 --- a/zatacka.js +++ b/zatacka.js @@ -43,13 +43,16 @@ const Zatacka = (function(window, document) { return config.keys.quit.indexOf(key) !== -1; } + function defaultPlayerData(id) { + return config.defaultPlayers.find(defaultPlayer => defaultPlayer.id === id); + } + function defaultPlayer(id) { - for (let i = 0; i < config.defaultPlayers.length; i++) { - let dp = config.defaultPlayers[i]; - if (dp.id === id) { - return new Player(dp.id, dp.name, dp.color, dp.keyL, dp.keyR); - } + const playerData = defaultPlayerData(id); + if (playerData === undefined) { + throw new TypeError(`There is no default player with ID ${id}.`); } + return new Player(playerData.id, playerData.name, playerData.color, playerData.keyL, playerData.keyR); } function proceedKeyPressedInLobby() { From bf7abf6bd124165d931607792b999db17ace1189 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 27 Mar 2016 12:36:20 +0200 Subject: [PATCH 075/168] Capitalize filenames; rename to ZATACKA.html --- zatacka.html => ZATACKA.html | 4 ++-- zatacka.css => Zatacka.css | 0 zatacka.js => Zatacka.js | 0 lib/{utilities.js => Utilities.js} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename zatacka.html => ZATACKA.html (96%) rename zatacka.css => Zatacka.css (100%) rename zatacka.js => Zatacka.js (100%) rename lib/{utilities.js => Utilities.js} (100%) diff --git a/zatacka.html b/ZATACKA.html similarity index 96% rename from zatacka.html rename to ZATACKA.html index 1069754..b1545fa 100644 --- a/zatacka.html +++ b/ZATACKA.html @@ -6,7 +6,7 @@ Achtung, die Kurve! - + @@ -83,7 +83,7 @@ - + diff --git a/zatacka.css b/Zatacka.css similarity index 100% rename from zatacka.css rename to Zatacka.css diff --git a/zatacka.js b/Zatacka.js similarity index 100% rename from zatacka.js rename to Zatacka.js diff --git a/lib/utilities.js b/lib/Utilities.js similarity index 100% rename from lib/utilities.js rename to lib/Utilities.js From 320a001113fd9036abd7c0b0ddb1b2304c607440 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 27 Mar 2016 13:02:21 +0200 Subject: [PATCH 076/168] Rewrite almost entire readme --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 666ceeb..8ff5848 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,23 @@ # Achtung, die Kurve! in JavaScript -The aim of this project is to create a HTML5/JS remake of the original *Achtung, die Kurve!* from 1995. +The aim of this project is to create an HTML5/JS remake of the original *Achtung, die Kurve!* from 1995. ## Play -Open `zatacka.html` in a browser to run the game. +Open `ZATACKA.html` in a browser to run the game. It will automatically scale itself up as much as possible; fullscreen is recommended for the best experience. ## Contribute -All actual code can be found in `zatacka.js`. The `scripts` folder contains some libraries that the game uses, but those will probably never be modified. +The main JS file is `Zatacka.js`. It is responsible for creating a `Game` and supplying a config, a `Renderer` and a `GUIController` to it. It also adds `Player`s to the `Game` and forwards key events to it. -Everything that happens in the black area is drawn on a canvas. `zatacka.html` and `zatacka.css` provide the scoreboard, chrome, alignment, and scaling. +`Game.js` controls the game logic. It knows which `Player`s are in the game and handles their interaction with eachother and the playing field, and it controls progress throughout the individual rounds. The `Game` constructor must be passed a config object, a `Renderer`, and a `GUIController`, respectively. + +`Player.js` describes a single player. It knows whether it is alive, where it is, where and how fast it is going, its score, and what `Game` it is attached to. + +`Renderer.js` does all the drawing on the actual playing field. `Game.js` does not draw anything; it just calls upon its `Renderer` to do that. + +`GUIController.js` controls the scoreboard and additional GUI features, such as hiding the lobby and showing the results when the game ends. When a player's score is changed, the `Game` will tell its `GUIController` to update the scoreboard entry of that player. + +`lib/Utilities.js` contains more general help functions. From b3b3fea202d00c3a122a01018d611fa7fb9f6ffb Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 27 Mar 2016 13:26:27 +0200 Subject: [PATCH 077/168] Add backend support for any number of L/R keys --- Player.js | 16 ++++++++++++---- lib/Utilities.js | 4 ++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Player.js b/Player.js index 9ad6b79..16e6ed7 100644 --- a/Player.js +++ b/Player.js @@ -21,12 +21,12 @@ class Player { this.angleChange = undefined; this.maxTicksBeforeDraw = undefined; if (isPositiveInt(keyL)) { - this.keyL = keyL; + this.L_keys = [keyL]; } else { logWarning(`Creating player "${this.name}" without a LEFT key.`); } if (isPositiveInt(keyR)) { - this.keyR = keyR; + this.R_keys = [keyR]; } else { logWarning(`Creating player "${this.name}" without a RIGHT key.`); } @@ -51,6 +51,14 @@ class Player { return false; // TODO } + isPressingLeft() { + return anyKeyBeingPressed(this.L_keys); + } + + isPressingRight() { + return anyKeyBeingPressed(this.R_keys); + } + // GETTERS @@ -161,10 +169,10 @@ class Player { let debugField = document.getElementById(debugFieldID); debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y)+", dir = "+round(radToDeg(this.direction), 2); if (this.isAlive()) { - if (Keyboard.isDown(this.keyL)) { + if (this.isPressingLeft()) { this.direction += this.angleChange; } - if (Keyboard.isDown(this.keyR)) { + if (this.isPressingRight()) { this.direction -= this.angleChange; } // We want the direction to stay in the interval -pi < dir <= pi: diff --git a/lib/Utilities.js b/lib/Utilities.js index a57f7da..70c5aaa 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -98,3 +98,7 @@ const Keyboard = { } }; +function anyKeyBeingPressed(keyCodes) { + return keyCodes.filter(Keyboard.isDown, Keyboard).length > 0; +} + From 6a7428af7ab9ca6f7f5e2397db348e59ca9cc984 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 27 Mar 2016 14:48:39 +0200 Subject: [PATCH 078/168] Improve Player constructor and setGame semantics --- Player.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Player.js b/Player.js index 16e6ed7..2ffce10 100644 --- a/Player.js +++ b/Player.js @@ -1,24 +1,24 @@ "use strict"; class Player { - constructor(id, name = "Player", color = "white", keyL = undefined, keyR = undefined) { + constructor(id, name = `Player ${id}`, color = "white", keyL = undefined, keyR = undefined) { if (!isPositiveInt(id)) { throw new TypeError(`Cannot create a player with ID ${id}.`); } this.id = id; this.name = name; this.color = color; - this.queuedDraws = new Queue(); this.alive = false; + this.score = 0; this.x = null; this.y = null; - this.lastDraw = null; this.direction = 0; this.velocity = 0; + this.lastDraw = null; + this.queuedDraws = new Queue(); this.game = undefined; - this.score = 0; this.config = undefined; - this.angleChange = undefined; + this.angleChangePerTick = undefined; this.maxTicksBeforeDraw = undefined; if (isPositiveInt(keyL)) { this.L_keys = [keyL]; @@ -90,9 +90,12 @@ class Player { // SETTERS setGame(game) { + if (!(game instanceof Game)) { + throw new TypeError(`Cannot connect ${this} to ${game} because it is not a Game.`); + } this.game = game; this.config = game.config; - this.angleChange = game.computeAngleChange(); + this.angleChangePerTick = game.computeAngleChange(); this.maxTicksBeforeDraw = Math.max(Math.floor(this.config.tickrate/this.config.speed), 1); } @@ -170,10 +173,10 @@ class Player { debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y)+", dir = "+round(radToDeg(this.direction), 2); if (this.isAlive()) { if (this.isPressingLeft()) { - this.direction += this.angleChange; + this.direction += this.angleChangePerTick; } if (this.isPressingRight()) { - this.direction -= this.angleChange; + this.direction -= this.angleChangePerTick; } // We want the direction to stay in the interval -pi < dir <= pi: this.direction = normalizeAngle(this.direction); From 84532dd0a91431692797afdaa6abcb27bc83af6a Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 27 Mar 2016 15:41:44 +0200 Subject: [PATCH 079/168] Move logic from Player to Game As of this commit, a Player does not need to know what Game it is attached to, as everything related to the Game is handled there. --- Game.js | 74 ++++++++++++++++++++++++++++++++++++++++----- Player.js | 89 +++++++++++-------------------------------------------- 2 files changed, 83 insertions(+), 80 deletions(-) diff --git a/Game.js b/Game.js index 7036e36..c4ab3d8 100644 --- a/Game.js +++ b/Game.js @@ -69,6 +69,10 @@ class Game { return Math.round(coordinate - this.config.thickness/2); } + computeMaxTicksBeforeDraw() { + return Math.max(Math.floor(this.config.tickrate/this.config.speed), 1); + } + // Computes the angle change for one tick when turning, in radians: computeAngleChange() { return this.config.speed / (this.config.tickrate * this.config.turningRadius); @@ -259,7 +263,7 @@ class Game { if (!this.hasPlayer(player.getID())) { log(`${player} ready!`); this.players.push(player); - player.setGame(this); + player.setMaxSpeed(this.config.speed); this.GUI_playerReady(player.getID()); } else { logWarning(`Not adding ${player} to the game because there is already a player with ID ${player.getID()}.`); @@ -347,14 +351,46 @@ class Game { this.Render_drawSquare(left, top, player.getColor()); } + flicker(player) { + const stopFlickering = () => { + clearInterval(flickerTicker); + let left = this.edgeOfSquare(player.x); + let top = this.edgeOfSquare(player.y); + this.Render_drawSquare(left, top, player.getColor()); + } + const self = this; + const left = this.edgeOfSquare(player.x); + const top = this.edgeOfSquare(player.y); + const color = player.getColor(); + let isVisible = false; + let flickerTicker = setInterval(() => { + if (isVisible) { + this.Render_clearSquare(left, top); + } else { + this.Render_drawSquare(left, top, color); + } + isVisible = !isVisible; + }, 1000/this.config.flickerFrequency); + setTimeout(stopFlickering, self.config.flickerDuration); + } + + spawn(player, position, direction) { + log(`${player} spawning at (${round(position.x, 2)}, ${round(position.y, 2)}).`); + player.x = position.x; + player.y = position.y; + player.direction = direction; + player.occupy(this.edgeOfSquare(player.x), this.edgeOfSquare(player.y)); + this.flicker(player); + } + /** Spawns and then starts all players. */ spawnAndStartPlayers() { const self = this; // Spawn each player, then wait for it to finish flickering before spawning the next one: (function spawnPlayer(i) { if (i < self.players.length) { - self.players[i].spawn(self.randomSpawnPosition(), self.randomSpawnAngle()); - setTimeout(() => { spawnPlayer(++i); }, self.config.flickerDuration); + self.spawn(self.players[i], self.randomSpawnPosition(), self.randomSpawnAngle()); + setTimeout(() => spawnPlayer(++i), self.config.flickerDuration); } else { // All players have spawned. Start them! self.startPlayers(); @@ -449,6 +485,32 @@ class Game { // MAIN LOOP + + + updatePlayer(player, delta) { + if (player.isAlive()) { + // Debugging: + const debugFieldID = "debug_" + player.getName().toLowerCase(); + const debugField = document.getElementById(debugFieldID); + let direction = player.getDirection(); + debugField.textContent = "x ~ "+Math.round(player.x)+", y ~ "+Math.round(player.y)+", dir = "+round(radToDeg(player.direction), 2); + if (player.isPressingLeft()) { + direction += this.computeAngleChange(); + } + if (player.isPressingRight()) { + direction -= this.computeAngleChange(); + } + // We use normalizeAngle so the angle stays in the interval -pi < dir <= pi: + player.setDirection(normalizeAngle(direction)); + const theta = player.getVelocity() * delta / 1000; + player.x += theta * Math.cos(player.direction); + player.y -= theta * Math.sin(player.direction); + if (this.totalNumberOfTicks % this.computeMaxTicksBeforeDraw() === 0) { + player.enqueueDraw(); + } + } + } + /** * Updates everything on each tick. * @param {Number} delta @@ -456,11 +518,7 @@ class Game { */ update(delta) { this.Render_clearHeads(); - for (let i = 0; i < this.players.length; i++) { - if (this.players[i].isAlive()) { - this.players[i].update(delta, this.totalNumberOfTicks); - } - } + this.players.forEach((player) => { this.updatePlayer(player, delta); }); this.totalNumberOfTicks++; // Cycle players so the players take turns being prioritized: if (this.isLive()) { diff --git a/Player.js b/Player.js index 2ffce10..5ba8d40 100644 --- a/Player.js +++ b/Player.js @@ -16,10 +16,6 @@ class Player { this.velocity = 0; this.lastDraw = null; this.queuedDraws = new Queue(); - this.game = undefined; - this.config = undefined; - this.angleChangePerTick = undefined; - this.maxTicksBeforeDraw = undefined; if (isPositiveInt(keyL)) { this.L_keys = [keyL]; } else { @@ -86,64 +82,32 @@ class Player { return this.score; } + getVelocity() { + return this.velocity; + } + + getDirection() { + return this.direction; + } + // SETTERS - setGame(game) { - if (!(game instanceof Game)) { - throw new TypeError(`Cannot connect ${this} to ${game} because it is not a Game.`); - } - this.game = game; - this.config = game.config; - this.angleChangePerTick = game.computeAngleChange(); - this.maxTicksBeforeDraw = Math.max(Math.floor(this.config.tickrate/this.config.speed), 1); + setMaxSpeed(speed) { + this.maxSpeed = speed; + } + + setDirection(direction) { + this.direction = direction; } // DOERS - flicker() { - let isVisible = false; - let left = this.game.edgeOfSquare(this.x); - let top = this.game.edgeOfSquare(this.y); - let color = this.getColor(); - this.flickerTicker = setInterval(() => { - if (isVisible) { - this.game.Render_clearSquare(left, top); - isVisible = false; - } else { - this.game.Render_drawSquare(left, top, color); - isVisible = true; - } - }, 1000/this.config.flickerFrequency); - } - - stopFlickering() { - clearInterval(this.flickerTicker); - let left = this.game.edgeOfSquare(this.x); - let top = this.game.edgeOfSquare(this.y); - this.game.Render_drawSquare(left, top, this.color); - } - - spawn(position, direction) { - if (!(this.game instanceof Game)) { - throw new TypeError(`${this} is not attached to any game.`); - } else { - log(`${this} spawning at (${round(position.x, 2)}, ${round(position.y, 2)}).`); - this.x = position.x; - this.y = position.y; - this.direction = direction; - this.flicker(); - let self = this; - setTimeout(() => { self.stopFlickering(); }, self.config.flickerDuration); - this.occupy(this.game.edgeOfSquare(this.x), this.game.edgeOfSquare(this.y)); - } - } - start() { log(`${this} starting.`); this.alive = true; - this.velocity = this.config.speed; + this.velocity = this.maxSpeed; } incrementScore() { @@ -166,26 +130,7 @@ class Player { }; } - update(delta, totalNumberOfTicks) { - // Debugging: - let debugFieldID = "debug_" + this.getName().toLowerCase(); - let debugField = document.getElementById(debugFieldID); - debugField.textContent = "x ~ "+Math.round(this.x)+", y ~ "+Math.round(this.y)+", dir = "+round(radToDeg(this.direction), 2); - if (this.isAlive()) { - if (this.isPressingLeft()) { - this.direction += this.angleChangePerTick; - } - if (this.isPressingRight()) { - this.direction -= this.angleChangePerTick; - } - // We want the direction to stay in the interval -pi < dir <= pi: - this.direction = normalizeAngle(this.direction); - let theta = this.velocity * delta / 1000; - this.x = this.x + theta * Math.cos(this.direction); - this.y = this.y - theta * Math.sin(this.direction); - if (totalNumberOfTicks % this.maxTicksBeforeDraw === 0) { // TODO - this.queuedDraws.enqueue({"x": this.x, "y": this.y }); - } - } + enqueueDraw() { + this.queuedDraws.enqueue({ "x": this.x, "y": this.y }); } } From 807ca143bf4bcb63dd5d6e2a9d90a6fafd103489 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 00:16:13 +0200 Subject: [PATCH 080/168] Make pixels a Uint8Array and improve addPlayer --- Game.js | 28 ++++++++++++++++------------ Player.js | 2 +- lib/Utilities.js | 4 ++++ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Game.js b/Game.js index c4ab3d8..58562d5 100644 --- a/Game.js +++ b/Game.js @@ -24,7 +24,7 @@ class Game { // Instance variables: this.config = config; - this.pixels = (new Array(this.config.width * this.config.height)).fill(-1); + this.pixels = new Uint8Array(this.config.width * this.config.height); // Limits the number of players to 255, which should be enough. this.players = []; this.rounds = []; this.renderer = renderer; @@ -69,6 +69,10 @@ class Game { return Math.round(coordinate - this.config.thickness/2); } + maxPlayers() { + return numberOfBytesToMaxValue(this.pixels.BYTES_PER_ELEMENT); + } + computeMaxTicksBeforeDraw() { return Math.max(Math.floor(this.config.tickrate/this.config.speed), 1); } @@ -213,7 +217,6 @@ class Game { } isOccupiedPixelAddress(addr) { - // TODO ????? return this.pixels[addr] > 0; } @@ -259,17 +262,18 @@ class Game { * @param {Player} player The player to add. */ addPlayer(player) { - if (Player.isPlayer(player)) { - if (!this.hasPlayer(player.getID())) { - log(`${player} ready!`); - this.players.push(player); - player.setMaxSpeed(this.config.speed); - this.GUI_playerReady(player.getID()); - } else { - logWarning(`Not adding ${player} to the game because there is already a player with ID ${player.getID()}.`); - } - } else { + const maxPlayers = this.maxPlayers(); + if (!Player.isPlayer(player)) { throw new TypeError(`Cannot add ${player} to the game because it is not a player.`); + } else if (player.getID() > maxPlayers) { + throw new RangeError(`Cannot add ${player} to the game because player IDs larger than ${maxPlayers} are not supported.`); + } else if (this.hasPlayer(player.getID())) { + logWarning(`Not adding ${player} to the game because there is already a player with ID ${player.getID()}.`); + } else { + log(`${player} ready!`); + this.players.push(player); + player.setMaxSpeed(this.config.speed); + this.GUI_playerReady(player.getID()); } } diff --git a/Player.js b/Player.js index 5ba8d40..7feae2a 100644 --- a/Player.js +++ b/Player.js @@ -3,7 +3,7 @@ class Player { constructor(id, name = `Player ${id}`, color = "white", keyL = undefined, keyR = undefined) { if (!isPositiveInt(id)) { - throw new TypeError(`Cannot create a player with ID ${id}.`); + throw new TypeError(`Cannot create a player with ID ${id}. Only positive integers are accepted.`); } this.id = id; this.name = name; diff --git a/lib/Utilities.js b/lib/Utilities.js index 70c5aaa..ad3679f 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -85,6 +85,10 @@ function flush(node) { } } +function numberOfBytesToMaxValue(bytes) { + return Math.pow(2, bytes * 8) - 1; +} + const Keyboard = { pressed: {}, isDown: function(keyCode) { From 28250628731c4442781c726a9690b3b35df7004a Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 00:16:46 +0200 Subject: [PATCH 081/168] Remove redundant try ... catch in GUIController --- GUIController.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/GUIController.js b/GUIController.js index d3d9d24..f2c4e3f 100644 --- a/GUIController.js +++ b/GUIController.js @@ -26,20 +26,12 @@ function GUIController(cfg) { function playerReady(id) { const index = id - 1; - try { - controls.children[index].children[1].classList.add(CLASS_ACTIVE); - } catch (error) { - console.error(error); - } + controls.children[index].children[1].classList.add(CLASS_ACTIVE); } function playerUnready(id) { const index = id - 1; - try { - controls.children[index].children[1].classList.remove(CLASS_ACTIVE); - } catch (error) { - console.error(error); - } + controls.children[index].children[1].classList.remove(CLASS_ACTIVE); } function gameStarted() { From 6cd59f2fb3a8bea8154d7c8e6af8492bfb79e9fb Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 00:48:03 +0200 Subject: [PATCH 082/168] Replace two anonymous functions with arrow functions --- Zatacka.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zatacka.js b/Zatacka.js index e6d18cd..457f60e 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -1,6 +1,6 @@ "use strict"; -const Zatacka = (function(window, document) { +const Zatacka = ((window, document) => { const canvas = byID("canvas"); @@ -132,7 +132,7 @@ const Zatacka = (function(window, document) { const game = new Game(config, Renderer(config, canvas), GUIController(config)); return { - getConfig: function() { return config; } + getConfig: () => config }; })(window, document); From e390a4bf4e28c93fe06d06dda632f5d6a9bd83a6 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 11:30:36 +0200 Subject: [PATCH 083/168] Remove slow `let` compound statements in Game V8 does not support optimization of compound assignments on variables declared with `let` at the time of this commit (Chrome 49.0.2623). For this reason, we will not use them in performance-critical functions, since the entire function will be left unoptimized if it contains an unsupported construct. We won't use: let x = a; x += b; We will instead use: let x = a; x = x + b; https://jsperf.com/let-compound-assignment --- Game.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Game.js b/Game.js index 58562d5..ac6cde9 100644 --- a/Game.js +++ b/Game.js @@ -496,13 +496,14 @@ class Game { // Debugging: const debugFieldID = "debug_" + player.getName().toLowerCase(); const debugField = document.getElementById(debugFieldID); + const angleChange = this.computeAngleChange(); let direction = player.getDirection(); debugField.textContent = "x ~ "+Math.round(player.x)+", y ~ "+Math.round(player.y)+", dir = "+round(radToDeg(player.direction), 2); if (player.isPressingLeft()) { - direction += this.computeAngleChange(); + direction = direction + angleChange; // let compound assignment not optimizable in V8 } if (player.isPressingRight()) { - direction -= this.computeAngleChange(); + direction = direction - angleChange; // let compound assignment not optimizable in V8 } // We use normalizeAngle so the angle stays in the interval -pi < dir <= pi: player.setDirection(normalizeAngle(direction)); From 2c26a4efaa814f10cb5f3b4e1ca6a68182d31f14 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 11:53:02 +0200 Subject: [PATCH 084/168] Replace var with let/const in Utilities --- lib/Utilities.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Utilities.js b/lib/Utilities.js index ad3679f..a10bf0a 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -44,11 +44,11 @@ function randomFloat(min, max) { } function normalizeAngle(a) { - var pi = Math.PI; - var angle = a % (2*pi); + const pi = Math.PI; + let angle = a % (2*pi); angle = (angle + 2*pi) % (2*pi); if (angle > pi) { - angle -= 2*pi; + angle = angle - 2*pi; } return angle; } From d6a2a25e93b65c09e0ec88bcaa43f132fe4da955 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 13:14:18 +0200 Subject: [PATCH 085/168] Replace for loop with forEach() in draw() --- Game.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Game.js b/Game.js index ac6cde9..73ebe99 100644 --- a/Game.js +++ b/Game.js @@ -535,9 +535,7 @@ class Game { * Draws all players. */ draw() { - for (let i = 0; i < this.players.length; i++) { - this.drawPlayer(this.players[i]); - } + this.players.forEach(this.drawPlayer, this); } /** From 178b37bf8362ce615fdafd5f77e065c8d0b5204d Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 13:18:29 +0200 Subject: [PATCH 086/168] Replace for loop with some() in isCrashing() --- Game.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Game.js b/Game.js index 73ebe99..f1b225b 100644 --- a/Game.js +++ b/Game.js @@ -221,13 +221,8 @@ class Game { } isCrashing(player, left, top) { - let hitboxPixels = this.computeHitbox(player, left, top); - for (let i = 0; i < hitboxPixels.length; i++) { - if (this.isOccupiedPixelAddress(hitboxPixels[i])) { - return true; - } - } - return false; + const hitboxPixels = this.computeHitbox(player, left, top); + return hitboxPixels.some(this.isOccupiedPixelAddress, this); } /** From 9d5fa92cb390467916696dab3c5d9d1cdf7cbd42 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 13:23:38 +0200 Subject: [PATCH 087/168] Replace for loop with some() in hasPlayer() --- Game.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Game.js b/Game.js index f1b225b..cf9ae9a 100644 --- a/Game.js +++ b/Game.js @@ -242,11 +242,7 @@ class Game { * @param {Number} id The ID to check for. */ hasPlayer(id) { - for (let i = 0; i < this.players.length; i++) { - if (this.players[i].getID() === id) { - return true; - } - } + return this.players.some((player) => player.getID() === id); } From 93196f86e5c7620d1678cc8f551654a94d57f0ed Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 13:34:45 +0200 Subject: [PATCH 088/168] Replace for loop with forEach() in removePlayer() --- Game.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Game.js b/Game.js index cf9ae9a..a5f1c55 100644 --- a/Game.js +++ b/Game.js @@ -273,14 +273,15 @@ class Game { * @param {Number} id The ID of the player to remove. */ removePlayer(id) { - for (let i = 0; i < this.players.length; i++) { - let player = this.players[i]; + this.players.forEach((player, index) => { if (player.getID() === id) { log(`${player} unready!`); - this.players.splice(i, 1); + this.players[index] = null; this.GUI_playerUnready(id); } - } + }, this); + // this.players may now contain a null value that we do not want to keep: + this.players = this.players.filter(Player.isPlayer); } /** Starts the game. */ From 7d6a21a5f406f182b5e212b0b38d399d31c98d49 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 13:38:07 +0200 Subject: [PATCH 089/168] Replace for loop with forEach() in start() --- Game.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Game.js b/Game.js index a5f1c55..2d1a647 100644 --- a/Game.js +++ b/Game.js @@ -288,10 +288,9 @@ class Game { start() { if (this.isCompetitive()) { this.setTargetScore(this.constructor.calculateTargetScore(this.getNumberOfActivePlayers())); - for (let i = 0; i < this.players.length; i++) { - let player = this.players[i]; + this.players.forEach((player) => { this.GUI_updateScoreOfPlayer(player.getID(), player.getScore()); - } + }); } log("Starting game!"); this.started = true; From 3575b59760ce575f24f2ee498c23dda16ffaaf84 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 13:40:21 +0200 Subject: [PATCH 090/168] Replace for loop with forEach() in startPlayers() --- Game.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Game.js b/Game.js index 2d1a647..7ef7752 100644 --- a/Game.js +++ b/Game.js @@ -325,11 +325,13 @@ class Game { this.players.sort((a, b) => (a.getID() - b.getID())); } + startPlayer(player) { + player.start(); + } + /** Starts all players. */ startPlayers() { - for (let i = 0; i < this.players.length; i++) { - this.players[i].start(); - } + this.players.forEach(this.startPlayer); this.live = true; } From e9da46f512d3f02f45504af6df3752c4a5443bd1 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 14:16:00 +0200 Subject: [PATCH 091/168] Add forfor() --- lib/Utilities.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Utilities.js b/lib/Utilities.js index a10bf0a..5073d92 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -85,6 +85,14 @@ function flush(node) { } } +function forfor(y_start, y_end, x_start, x_end, func, arg) { + for (let y = y_start; y < y_end; y++) { + for (let x = x_start; x < x_end; x++) { + func.call(this, x, y, arg); + } + } +} + function numberOfBytesToMaxValue(bytes) { return Math.pow(2, bytes * 8) - 1; } From 08bf85fdc130f916b5f50313aa17f1e863e35bd7 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 14:16:42 +0200 Subject: [PATCH 092/168] Replace for loop with forfor() in occupy() --- Game.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Game.js b/Game.js index 7ef7752..b7cf3f7 100644 --- a/Game.js +++ b/Game.js @@ -335,16 +335,16 @@ class Game { this.live = true; } + occupyPixel(x, y, id) { + this.pixels[this.pixelAddress(x, y)] = id; + } + occupy(player, left, top) { player.occupy(left, top); let right = left + this.config.thickness; let bottom = top + this.config.thickness; let id = player.getID(); - for (let y = top; y < bottom; y++) { - for (let x = left; x < right; x++) { - this.pixels[this.pixelAddress(x, y)] = id; - } - } + forfor(top, bottom, left, right, this.occupyPixel.bind(this), id); this.Render_drawSquare(left, top, player.getColor()); } From 9c897696bdbae65407ad3c2037fa143626c0a5c7 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 14:46:29 +0200 Subject: [PATCH 093/168] Rewrite keyPressedInLobby() with forEach() --- Zatacka.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Zatacka.js b/Zatacka.js index 457f60e..ee9a168 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -65,17 +65,20 @@ const Zatacka = ((window, document) => { } } - function keyPressedInLobby(pressedKey) { - for (let i = 0; i < config.defaultPlayers.length; i++) { - let player = config.defaultPlayers[i]; - if (pressedKey === player.keyL) { - game.addPlayer(defaultPlayer(player.id)); - } else if (pressedKey === player.keyR) { - game.removePlayer(player.id); - } + function addOrRemovePlayer(playerData, pressedKey) { + if (pressedKey === playerData.keyL) { + game.addPlayer(defaultPlayer(playerData.id)); + } else if (pressedKey === playerData.keyR) { + game.removePlayer(playerData.id); } } + function keyPressedInLobby(pressedKey) { + config.defaultPlayers.forEach((playerData) => { + addOrRemovePlayer(playerData, pressedKey); + }); + } + function lobbyKeyHandler(event) { let pressedKey = event.keyCode; if (isProceedKey(pressedKey)) { From 4e77bc7b162f57bc53bafa20962398176a58fbf9 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 15:27:55 +0200 Subject: [PATCH 094/168] Add exception handling to player[Un]Ready() --- GUIController.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/GUIController.js b/GUIController.js index f2c4e3f..5c29de6 100644 --- a/GUIController.js +++ b/GUIController.js @@ -21,17 +21,31 @@ function GUIController(cfg) { lobby.classList.add(CLASS_HIDDEN); } + function isLobbyEntry(element) { + return isHTMLElement(element) && element.children.length >= 2; + } + // PUBLIC API function playerReady(id) { const index = id - 1; - controls.children[index].children[1].classList.add(CLASS_ACTIVE); + const entry = controls.children[index]; + if (!isLobbyEntry(entry)) { + logWarning(`Cannot mark player ${id} as ready because controls.children[${index}] (${controls.children[index]}) is not a valid lobby entry.`); + } else { + entry.children[1].classList.add(CLASS_ACTIVE); + } } function playerUnready(id) { const index = id - 1; - controls.children[index].children[1].classList.remove(CLASS_ACTIVE); + const entry = controls.children[index]; + if (!isLobbyEntry(entry)) { + logWarning(`Cannot mark player ${id} as unready because controls.children[${index}] (${controls.children[index]}) is not a valid lobby entry.`); + } else { + entry.children[1].classList.remove(CLASS_ACTIVE); + } } function gameStarted() { From ea8e3450b75a63157eb44dd7ea4b282179ff105d Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 15:29:29 +0200 Subject: [PATCH 095/168] Make anyKeyBeingPressed() accept non-array input Before this commit, `anyKeyBeingPressed()` would throw an exception if the `keyCodes` input argument was not an array. This commit improves the function so that it returns false in those cases. --- lib/Utilities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Utilities.js b/lib/Utilities.js index 5073d92..2798390 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -111,6 +111,6 @@ const Keyboard = { }; function anyKeyBeingPressed(keyCodes) { - return keyCodes.filter(Keyboard.isDown, Keyboard).length > 0; + return Array.isArray(keyCodes) && keyCodes.filter(Keyboard.isDown, Keyboard).length > 0; } From 763fd7eca0994c96addb228d2fd94a612f3ee835 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 15:35:21 +0200 Subject: [PATCH 096/168] Add DOM exception prevention to updatePlayer() --- Game.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Game.js b/Game.js index b7cf3f7..b135131 100644 --- a/Game.js +++ b/Game.js @@ -491,7 +491,9 @@ class Game { const debugField = document.getElementById(debugFieldID); const angleChange = this.computeAngleChange(); let direction = player.getDirection(); - debugField.textContent = "x ~ "+Math.round(player.x)+", y ~ "+Math.round(player.y)+", dir = "+round(radToDeg(player.direction), 2); + if (isHTMLElement(debugField)) { + debugField.textContent = "x ~ "+Math.round(player.x)+", y ~ "+Math.round(player.y)+", dir = "+round(radToDeg(player.direction), 2); + } if (player.isPressingLeft()) { direction = direction + angleChange; // let compound assignment not optimizable in V8 } From 58b4c166c7be01eabfb32dd4d71ece994195dd23 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 15:36:16 +0200 Subject: [PATCH 097/168] Add getGame() and addPlayer() to public API --- Zatacka.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Zatacka.js b/Zatacka.js index ee9a168..d7585ce 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -135,7 +135,12 @@ const Zatacka = ((window, document) => { const game = new Game(config, Renderer(config, canvas), GUIController(config)); return { - getConfig: () => config + getConfig: () => config, + getGame: () => game, + addPlayer: (playerOrID) => { + const player = Player.isPlayer(playerOrID) ? playerOrID : new Player(playerOrID); + game.addPlayer(player); + } }; })(window, document); From ab70863148101e114e99053b5f32b824dcd9ac87 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 16:54:43 +0200 Subject: [PATCH 098/168] Add dev/ and css-generator.js dev/ is intended for development tools only; it is NOT NEEDED at runtime. css-generator.js contains a function, `generateCSS()`, that generates CSS for different scaling factors. --- dev/css-generator.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 dev/css-generator.js diff --git a/dev/css-generator.js b/dev/css-generator.js new file mode 100644 index 0000000..80d2a6c --- /dev/null +++ b/dev/css-generator.js @@ -0,0 +1,30 @@ +function generateCSS() { + const defaultWidth = 640; + const defaultHeight = 480; + const leftWidth = 80; + const maxScaling = 5; + + let css = ""; + for (let i = 1; i < maxScaling; i++) { + css += `/* ======== ${i}x ======== */ + @media screen and (min-width: ${i*defaultWidth}px) and (min-height: ${i*defaultHeight}px) { + body { justify-content: flex-end; } + #wrapper { + transform: scale(${i}); + transform-origin: right; + } + } + + @media screen and (min-width: ${i*(defaultWidth+leftWidth)}px) and (min-height: ${i*defaultHeight}px) { + body { justify-content: center; } + #wrapper { + transform-origin: center; + } + } + + + `; + } + + return css; +} \ No newline at end of file From 4f0a767ae20bf89a6a96e224db7ccbe137602e1c Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 17:00:13 +0200 Subject: [PATCH 099/168] Replace getID() === id with hasID(id) --- Game.js | 2 +- Player.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Game.js b/Game.js index b135131..064daba 100644 --- a/Game.js +++ b/Game.js @@ -242,7 +242,7 @@ class Game { * @param {Number} id The ID to check for. */ hasPlayer(id) { - return this.players.some((player) => player.getID() === id); + return this.players.some((player) => player.hasID(id)); } diff --git a/Player.js b/Player.js index 7feae2a..e4488ec 100644 --- a/Player.js +++ b/Player.js @@ -55,6 +55,10 @@ class Player { return anyKeyBeingPressed(this.R_keys); } + hasID(id) { + return this.id === id; + } + // GETTERS From 03665b7c0741d51bf73871e70ae9563094ab8c18 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 17:23:51 +0200 Subject: [PATCH 100/168] Add .gitignore with Sublime Text rules --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e379c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.sublime-project +*.sublime-workspace From f633b3bd36772f2dfad7e1bc5688a73eee6c6abf Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 28 Mar 2016 17:28:13 +0200 Subject: [PATCH 101/168] Update readme to describe `dev/` --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ff5848..f1c8709 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,6 @@ The main JS file is `Zatacka.js`. It is responsible for creating a `Game` and su `GUIController.js` controls the scoreboard and additional GUI features, such as hiding the lobby and showing the results when the game ends. When a player's score is changed, the `Game` will tell its `GUIController` to update the scoreboard entry of that player. -`lib/Utilities.js` contains more general help functions. +`lib/` contains **general libraries** and help functions. + +`dev/` may only contain **development tools** that are not needed at runtime, such as test suites or code generators. From 6f5a750473d0491cdf2f1e6ebc1723dbb14ce73c Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Thu, 31 Mar 2016 20:07:43 +0200 Subject: [PATCH 102/168] Remove imperative live and gameOver properties Instead of setting a `live` state imperatively, `isLive()` will check the actual criteria for a Game being live (such as number of live players > 1 for a competitive game) whenever it is called. Unless that constitutes a considerable performance hit, we prefer this approach because it is so much easier to understand, verify and debug. The same goes for `gameOver`. --- Game.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Game.js b/Game.js index 064daba..9ccf9a5 100644 --- a/Game.js +++ b/Game.js @@ -31,7 +31,6 @@ class Game { this.guiController = guiController; this.mode = this.constructor.DEFAULT_MODE; this.started = false; - this.live = false; this.waitingForNextRound = false; // TODO? this.waitingForKonecHry = false; // TODO? this.totalNumberOfTicks = 0; @@ -205,11 +204,7 @@ class Game { } isLive() { - return this.live; - } - isOver() { - return this.gameOver; } isCompetitive() { @@ -317,10 +312,6 @@ class Game { this.spawnAndStartPlayers(); } - endRound() { - this.live = false; - } - sortPlayers() { this.players.sort((a, b) => (a.getID() - b.getID())); } @@ -332,7 +323,6 @@ class Game { /** Starts all players. */ startPlayers() { this.players.forEach(this.startPlayer); - this.live = true; } occupyPixel(x, y, id) { From c2daea179e9a6b197fe7c16fe2869182ac25e1b5 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 1 Apr 2016 01:27:52 +0200 Subject: [PATCH 103/168] Add next round/game over/quit logic - Any proceed key (e.g. Space) now makes the game proceed to the next round/KONEC HRY iff the round is over. (KH is only a dummy function at the moment.) - Any quit key (e.g. Esc) reloads the app iff the round is over and KONEC HRY is not imminent. This functionality is written so as to be as declarative as possible, but any Game now has a `betweenRounds` property that must be set to true when the round ends, and to false when proceeding to the next round. --- Game.js | 100 +++++++++++++++++++++++++++++++++++++++++----------- Player.js | 9 +++++ Renderer.js | 7 +++- Zatacka.js | 7 +++- 4 files changed, 100 insertions(+), 23 deletions(-) diff --git a/Game.js b/Game.js index 9ccf9a5..aaccdd4 100644 --- a/Game.js +++ b/Game.js @@ -31,8 +31,7 @@ class Game { this.guiController = guiController; this.mode = this.constructor.DEFAULT_MODE; this.started = false; - this.waitingForNextRound = false; // TODO? - this.waitingForKonecHry = false; // TODO? + this.betweenRounds = false; // true when everyone is dead AFTER a round; not during the spawn procedure this.totalNumberOfTicks = 0; this.targetScore = null; this.initMainLoop(); @@ -163,6 +162,11 @@ class Game { return this.players.length; } + getNumberOfLivePlayers() { + const isAlive = player => player.isAlive(); + return this.players.filter(isAlive).length; + } + // SETTERS @@ -203,8 +207,26 @@ class Game { return this.started; } + isBetweenRounds() { + return this.betweenRounds; + } + isLive() { + if (this.isCompetitive()) { + return this.getNumberOfLivePlayers() > 1; + } else { + return this.getNumberOfLivePlayers() > 0; + } + } + // Caution: Returns true if called during the spawn procedure, since it only checks whether the game is live or not. + isRoundOver() { + return !this.isLive(); + } + + isGameOver() { + const hasReachedTargetScore = player => player.getScore() >= this.getTargetScore(); + return this.isCompetitive() && this.players.some(hasReachedTargetScore); } isCompetitive() { @@ -291,12 +313,12 @@ class Game { this.started = true; this.GUI_gameStarted(); MainLoop.start(); - this.nextRound(); + this.proceed(); } /** Quits the game. */ quit() { - window.reload(); + document.location.reload(); } /** Announce KONEC HRY, show results etc. */ @@ -304,12 +326,28 @@ class Game { log(this.constructor.KONEC_HRY); } - /** Proceeds to the next round */ - nextRound() { - // TODO - // Sort the players by their IDs so they spawn in the correct order: - this.sortPlayers(); - this.spawnAndStartPlayers(); + clearField() { + this.pixels.fill(0); + this.Render_clearField(); + } + + /** Proceeds to the next round (or KONEC HRY). */ + proceed() { + this.betweenRounds = false; + this.resetPlayers(); + if (this.isGameOver()) { + this.konecHry(); + } else { + this.clearField(); + // Sort the players by their IDs so they spawn in the correct order: + this.sortPlayers(); + this.spawnAndStartPlayers(); + } + } + + endRound() { + this.stopPlayers(); + this.betweenRounds = true; } sortPlayers() { @@ -320,11 +358,27 @@ class Game { player.start(); } + stopPlayer(player) { + player.stop(); + } + + resetPlayer(player) { + player.reset(); + } + /** Starts all players. */ startPlayers() { this.players.forEach(this.startPlayer); } + stopPlayers() { + this.players.forEach(this.stopPlayer); + } + + resetPlayers() { + this.players.forEach(this.resetPlayer); + } + occupyPixel(x, y, id) { this.pixels[this.pixelAddress(x, y)] = id; } @@ -427,19 +481,20 @@ class Game { // Check if round end player.die(cause); this.updateScores(); + if (this.isRoundOver()) { + this.endRound(); + } } - keyHandler(pressedKey) { - if (this.waitingForNextRound) { - if (this.isProceedKey(pressedKey)) { - this.nextRound(); - } else if (this.isQuitKey(pressedKey)) { - this.quit(); - } - } else if (this.waitingForKonecHry) { - if (this.isProceedKey(pressedKey)) { - this.konecHry(); - } + proceedKeyPressed() { + if (this.isBetweenRounds()) { + this.proceed(); + } + } + + quitKeyPressed() { + if (this.isBetweenRounds() && !this.isGameOver()) { + this.quit(); } } @@ -468,6 +523,9 @@ class Game { Render_clearSquare(left, top) { this.renderer.clearSquare(left, top, this.config.thickness); } + Render_clearField() { + this.renderer.clearRect(0, 0, this.config.width, this.config.height); + } // MAIN LOOP diff --git a/Player.js b/Player.js index e4488ec..3142eee 100644 --- a/Player.js +++ b/Player.js @@ -114,6 +114,15 @@ class Player { this.velocity = this.maxSpeed; } + stop() { + this.alive = false; + this.velocity = 0; + } + + reset() { + this.queuedDraws = new Queue(); + } + incrementScore() { this.score++; } diff --git a/Renderer.js b/Renderer.js index 0931ee1..7e75edd 100644 --- a/Renderer.js +++ b/Renderer.js @@ -14,9 +14,14 @@ function Renderer(cfg, canvas) { context.clearRect(left, top, size, size); } + function clearRect(left, top, width, height) { + context.clearRect(left, top, width, height); + } + return { drawSquare, - clearSquare + clearSquare, + clearRect }; } diff --git a/Zatacka.js b/Zatacka.js index d7585ce..5181b8d 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -93,7 +93,12 @@ const Zatacka = ((window, document) => { } function gameKeyHandler(event) { - game.keyHandler(event.keyCode); + let pressedKey = event.keyCode; + if (isProceedKey(pressedKey)) { + game.proceedKeyPressed(); + } else if (isQuitKey(pressedKey)) { + game.quitKeyPressed(); + } } function gameMouseHandler() { From 0b18d9460cec579f7b3fd2cd73ea285cbf72152c Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 1 Apr 2016 02:00:50 +0200 Subject: [PATCH 104/168] Remove debugging color cycler --- Game.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Game.js b/Game.js index aaccdd4..f782b9a 100644 --- a/Game.js +++ b/Game.js @@ -35,17 +35,6 @@ class Game { this.totalNumberOfTicks = 0; this.targetScore = null; this.initMainLoop(); - this.colors = ["red", "green", "white"]; - this.currentColor = 0; - } - - nextColor() { - let c = this.colors[this.currentColor]; - this.currentColor++; - if (this.currentColor >= this.colors.length) { - this.currentColor = 0; - } - return c; } static isRenderer(obj) { From a2fa83128bb7a30616c944943e457825e29e5fd3 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 1 Apr 2016 02:14:09 +0200 Subject: [PATCH 105/168] Add round count functionality --- Game.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Game.js b/Game.js index f782b9a..26a5e99 100644 --- a/Game.js +++ b/Game.js @@ -33,6 +33,7 @@ class Game { this.started = false; this.betweenRounds = false; // true when everyone is dead AFTER a round; not during the spawn procedure this.totalNumberOfTicks = 0; + this.currentRound = 0; this.targetScore = null; this.initMainLoop(); } @@ -323,10 +324,12 @@ class Game { /** Proceeds to the next round (or KONEC HRY). */ proceed() { this.betweenRounds = false; + this.currentRound++; this.resetPlayers(); if (this.isGameOver()) { this.konecHry(); } else { + log(`======== ROUND ${this.currentRound} ========`); this.clearField(); // Sort the players by their IDs so they spawn in the correct order: this.sortPlayers(); From e3c30ce3ebe00f7daa556a47923962b98aeba545 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 1 Apr 2016 11:26:19 +0200 Subject: [PATCH 106/168] Replace .filter().length > 0 with .some() --- lib/Utilities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Utilities.js b/lib/Utilities.js index 2798390..7ab6b52 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -111,6 +111,6 @@ const Keyboard = { }; function anyKeyBeingPressed(keyCodes) { - return Array.isArray(keyCodes) && keyCodes.filter(Keyboard.isDown, Keyboard).length > 0; + return Array.isArray(keyCodes) && keyCodes.some(Keyboard.isDown, Keyboard); } From e8479c3d2fde5b001aece1be724b38c0fb07d027 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 1 Apr 2016 11:32:05 +0200 Subject: [PATCH 107/168] Add special key (e.g. Alt) interception --- Zatacka.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Zatacka.js b/Zatacka.js index 5181b8d..51c27ce 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -23,7 +23,8 @@ const Zatacka = ((window, document) => { maxHoleLength: 12, // Kuxels keys: { "proceed": [KEY.SPACE, KEY.ENTER], - "quit": [KEY.ESCAPE] + "quit": [KEY.ESCAPE], + "special": [KEY.ALT] }, defaultPlayers: Object.freeze([ { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, @@ -43,6 +44,10 @@ const Zatacka = ((window, document) => { return config.keys.quit.indexOf(key) !== -1; } + function isSpecialKey(key) { + return config.keys.special.indexOf(key) !== -1; + } + function defaultPlayerData(id) { return config.defaultPlayers.find(defaultPlayer => defaultPlayer.id === id); } @@ -81,6 +86,9 @@ const Zatacka = ((window, document) => { function lobbyKeyHandler(event) { let pressedKey = event.keyCode; + if (isSpecialKey(pressedKey)) { + event.preventDefault(); + } if (isProceedKey(pressedKey)) { proceedKeyPressedInLobby(); } else { @@ -94,6 +102,9 @@ const Zatacka = ((window, document) => { function gameKeyHandler(event) { let pressedKey = event.keyCode; + if (isSpecialKey(pressedKey)) { + event.preventDefault(); + } if (isProceedKey(pressedKey)) { game.proceedKeyPressed(); } else if (isQuitKey(pressedKey)) { From f14cadc2e83f929ea7014f6fda937ca265e58cd2 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 2 Apr 2016 13:17:09 +0200 Subject: [PATCH 108/168] Add basic Round class --- Round.js | 16 ++++++++++++++++ ZATACKA.html | 1 + 2 files changed, 17 insertions(+) create mode 100644 Round.js diff --git a/Round.js b/Round.js new file mode 100644 index 0000000..b53963c --- /dev/null +++ b/Round.js @@ -0,0 +1,16 @@ +"use strict"; + +class Round { + constructor(players) { + // A list of the players in the order they died (winner also included): + this.results = []; + } + + add(player) { + this.results.push(player); + } + + getResults() { + return this.results; + } +} diff --git a/ZATACKA.html b/ZATACKA.html index b1545fa..bb6f9bc 100644 --- a/ZATACKA.html +++ b/ZATACKA.html @@ -85,6 +85,7 @@ + From 6b07b3f4ddace1b00812ce963003f96ef177ac6c Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 2 Apr 2016 21:48:00 +0200 Subject: [PATCH 109/168] Rewrite round and score handling After this commit, a Player no longer knows anything about its score. The score of any player is computed dynamically whenever it is needed by going through all past rounds checking the player's placement in each one. --- Game.js | 67 ++++++++++++++++++++++++++++++++++++------------------- Player.js | 9 -------- Round.js | 9 ++++++++ 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/Game.js b/Game.js index 26a5e99..a9454c1 100644 --- a/Game.js +++ b/Game.js @@ -31,9 +31,8 @@ class Game { this.guiController = guiController; this.mode = this.constructor.DEFAULT_MODE; this.started = false; - this.betweenRounds = false; // true when everyone is dead AFTER a round; not during the spawn procedure + this.postRound = false; // true when everyone is dead AFTER a round; not during the spawn procedure this.totalNumberOfTicks = 0; - this.currentRound = 0; this.targetScore = null; this.initMainLoop(); } @@ -157,6 +156,15 @@ class Game { return this.players.filter(isAlive).length; } + getScoreOfPlayer(id) { + const accumulateScore = (sum, round) => sum + round.getSuccessOfPlayer(id); + return this.rounds.reduce(accumulateScore, 0); + } + + getCurrentRound() { + return this.rounds[this.rounds.length - 1]; + } + // SETTERS @@ -197,8 +205,8 @@ class Game { return this.started; } - isBetweenRounds() { - return this.betweenRounds; + isPostRound() { + return this.postRound; } isLive() { @@ -215,7 +223,7 @@ class Game { } isGameOver() { - const hasReachedTargetScore = player => player.getScore() >= this.getTargetScore(); + const hasReachedTargetScore = player => this.getScoreOfPlayer(player.getID()) >= this.getTargetScore(); return this.isCompetitive() && this.players.some(hasReachedTargetScore); } @@ -296,7 +304,7 @@ class Game { if (this.isCompetitive()) { this.setTargetScore(this.constructor.calculateTargetScore(this.getNumberOfActivePlayers())); this.players.forEach((player) => { - this.GUI_updateScoreOfPlayer(player.getID(), player.getScore()); + this.GUI_updateScoreOfPlayer(player.getID(), 0); }); } log("Starting game!"); @@ -323,23 +331,27 @@ class Game { /** Proceeds to the next round (or KONEC HRY). */ proceed() { - this.betweenRounds = false; - this.currentRound++; - this.resetPlayers(); + this.postRound = false; + this.rounds.push(new Round()); if (this.isGameOver()) { this.konecHry(); } else { - log(`======== ROUND ${this.currentRound} ========`); - this.clearField(); - // Sort the players by their IDs so they spawn in the correct order: - this.sortPlayers(); - this.spawnAndStartPlayers(); + this.beginNewRound(); } } + beginNewRound() { + log(`======== ROUND ${this.rounds.length} ========`); + this.resetPlayers(); + this.clearField(); + // Sort the players by their IDs so they spawn in the correct order: + this.sortPlayers(); + this.spawnAndStartPlayers(); + } + endRound() { this.stopPlayers(); - this.betweenRounds = true; + this.postRound = true; } sortPlayers() { @@ -459,33 +471,42 @@ class Game { } } - updateScores() { + updateGUIScoreboard() { const isAlive = player => player.isAlive(); const updateScore = (player) => { - player.incrementScore(); - this.GUI_updateScoreOfPlayer(player.getID(), player.getScore()); + const id = player.getID(); + this.GUI_updateScoreOfPlayer(id, this.getScoreOfPlayer(id)); } this.players.filter(isAlive).forEach(updateScore); } death(player, cause) { - // Increment score and update it TODO - // Check if round end player.die(cause); - this.updateScores(); + this.getCurrentRound().add(player); + this.updateGUIScoreboard(); if (this.isRoundOver()) { + if (this.isCompetitive()) { + const isAlive = player => player.isAlive(); + const winner = this.players.find(isAlive); + this.win(winner); + } this.endRound(); } } + win(player) { + log(`${player} won the round.`); + this.getCurrentRound().add(player); + } + proceedKeyPressed() { - if (this.isBetweenRounds()) { + if (this.isPostRound()) { this.proceed(); } } quitKeyPressed() { - if (this.isBetweenRounds() && !this.isGameOver()) { + if (this.isPostRound() && !this.isGameOver()) { this.quit(); } } diff --git a/Player.js b/Player.js index 3142eee..1f85d23 100644 --- a/Player.js +++ b/Player.js @@ -9,7 +9,6 @@ class Player { this.name = name; this.color = color; this.alive = false; - this.score = 0; this.x = null; this.y = null; this.direction = 0; @@ -82,10 +81,6 @@ class Player { return this.lastDraw; } - getScore() { - return this.score; - } - getVelocity() { return this.velocity; } @@ -123,10 +118,6 @@ class Player { this.queuedDraws = new Queue(); } - incrementScore() { - this.score++; - } - /** * Called when the player does something that causes it do die. * @param {String} cause The cause of death. diff --git a/Round.js b/Round.js index b53963c..f685e52 100644 --- a/Round.js +++ b/Round.js @@ -10,6 +10,15 @@ class Round { this.results.push(player); } + getSuccessOfPlayer(id) { + for (let i = 0; i < this.results.length; i++) { + if (this.results[i].hasID(id)) { + return i; + } + } + return this.results.length; + } + getResults() { return this.results; } From 90405673acaba210e34590688b23a7fb9bd9e72a Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 2 Apr 2016 21:59:36 +0200 Subject: [PATCH 110/168] Make isAlive a static method in Game --- Game.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Game.js b/Game.js index a9454c1..689fc72 100644 --- a/Game.js +++ b/Game.js @@ -47,6 +47,10 @@ class Game { return obj !== undefined; } + static isAlive(player) { + return player.isAlive(); + } + static calculateTargetScore(numberOfPlayers) { // Default target score is (n-1) * 10 for n players: return (numberOfPlayers - 1) * 10; @@ -152,7 +156,7 @@ class Game { } getNumberOfLivePlayers() { - const isAlive = player => player.isAlive(); + const isAlive = this.constructor.isAlive; return this.players.filter(isAlive).length; } @@ -472,7 +476,7 @@ class Game { } updateGUIScoreboard() { - const isAlive = player => player.isAlive(); + const isAlive = this.constructor.isAlive; const updateScore = (player) => { const id = player.getID(); this.GUI_updateScoreOfPlayer(id, this.getScoreOfPlayer(id)); @@ -486,7 +490,7 @@ class Game { this.updateGUIScoreboard(); if (this.isRoundOver()) { if (this.isCompetitive()) { - const isAlive = player => player.isAlive(); + const isAlive = this.constructor.isAlive; const winner = this.players.find(isAlive); this.win(winner); } From 5a10fc3ff1aa6be0cdd1168b2858129ad9444f7c Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 3 Apr 2016 01:02:20 +0200 Subject: [PATCH 111/168] Remove property in Game --- Game.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Game.js b/Game.js index 689fc72..44850bd 100644 --- a/Game.js +++ b/Game.js @@ -30,7 +30,6 @@ class Game { this.renderer = renderer; this.guiController = guiController; this.mode = this.constructor.DEFAULT_MODE; - this.started = false; this.postRound = false; // true when everyone is dead AFTER a round; not during the spawn procedure this.totalNumberOfTicks = 0; this.targetScore = null; @@ -205,10 +204,6 @@ class Game { // CHECKERS - isStarted() { - return this.started; - } - isPostRound() { return this.postRound; } @@ -312,7 +307,6 @@ class Game { }); } log("Starting game!"); - this.started = true; this.GUI_gameStarted(); MainLoop.start(); this.proceed(); From f2764ceb8fc18eb9cc87c1448e90e2d6ce090f2c Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 3 Apr 2016 01:17:26 +0200 Subject: [PATCH 112/168] Make isPostRound more declarative --- Game.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Game.js b/Game.js index 44850bd..055cd53 100644 --- a/Game.js +++ b/Game.js @@ -30,7 +30,6 @@ class Game { this.renderer = renderer; this.guiController = guiController; this.mode = this.constructor.DEFAULT_MODE; - this.postRound = false; // true when everyone is dead AFTER a round; not during the spawn procedure this.totalNumberOfTicks = 0; this.targetScore = null; this.initMainLoop(); @@ -205,7 +204,7 @@ class Game { // CHECKERS isPostRound() { - return this.postRound; + return this.getCurrentRound().getResults().length === this.getNumberOfActivePlayers(); } isLive() { @@ -329,7 +328,6 @@ class Game { /** Proceeds to the next round (or KONEC HRY). */ proceed() { - this.postRound = false; this.rounds.push(new Round()); if (this.isGameOver()) { this.konecHry(); @@ -349,7 +347,6 @@ class Game { endRound() { this.stopPlayers(); - this.postRound = true; } sortPlayers() { From 62b693255e3fd698b37388ad2e1fe667a1dfd212 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 3 Apr 2016 01:18:54 +0200 Subject: [PATCH 113/168] Rename getNumberOf[Active]Players --- Game.js | 6 +++--- Zatacka.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Game.js b/Game.js index 055cd53..08ffb6e 100644 --- a/Game.js +++ b/Game.js @@ -149,7 +149,7 @@ class Game { return this.targetScore; } - getNumberOfActivePlayers() { + getNumberOfPlayers() { return this.players.length; } @@ -204,7 +204,7 @@ class Game { // CHECKERS isPostRound() { - return this.getCurrentRound().getResults().length === this.getNumberOfActivePlayers(); + return this.getCurrentRound().getResults().length === this.getNumberOfPlayers(); } isLive() { @@ -300,7 +300,7 @@ class Game { /** Starts the game. */ start() { if (this.isCompetitive()) { - this.setTargetScore(this.constructor.calculateTargetScore(this.getNumberOfActivePlayers())); + this.setTargetScore(this.constructor.calculateTargetScore(this.getNumberOfPlayers())); this.players.forEach((player) => { this.GUI_updateScoreOfPlayer(player.getID(), 0); }); diff --git a/Zatacka.js b/Zatacka.js index 51c27ce..256c67a 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -61,7 +61,7 @@ const Zatacka = ((window, document) => { } function proceedKeyPressedInLobby() { - const numberOfReadyPlayers = game.getNumberOfActivePlayers(); + const numberOfReadyPlayers = game.getNumberOfPlayers(); if (numberOfReadyPlayers > 0) { removeLobbyEventListeners(); addGameEventListeners(); From 1c5f831c75bf793053a4773a4501faba6cf12aa0 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 3 Apr 2016 01:46:57 +0200 Subject: [PATCH 114/168] Add full multikey support to Player --- Player.js | 20 +++++++++++++------- lib/Utilities.js | 6 ++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Player.js b/Player.js index 1f85d23..b1711c4 100644 --- a/Player.js +++ b/Player.js @@ -1,7 +1,7 @@ "use strict"; class Player { - constructor(id, name = `Player ${id}`, color = "white", keyL = undefined, keyR = undefined) { + constructor(id, name = `Player ${id}`, color = "white", L_keys, R_keys) { if (!isPositiveInt(id)) { throw new TypeError(`Cannot create a player with ID ${id}. Only positive integers are accepted.`); } @@ -15,15 +15,21 @@ class Player { this.velocity = 0; this.lastDraw = null; this.queuedDraws = new Queue(); - if (isPositiveInt(keyL)) { - this.L_keys = [keyL]; + + if (isPositiveInt(L_keys)) { + this.L_keys = [L_keys]; + } else if (isKeyList(L_keys)) { + this.L_keys = L_keys; } else { - logWarning(`Creating player "${this.name}" without a LEFT key.`); + logWarning(`Creating player "${this.name}" without any LEFT key(s).`); } - if (isPositiveInt(keyR)) { - this.R_keys = [keyR]; + + if (isPositiveInt(R_keys)) { + this.R_keys = [R_keys]; + } else if (isKeyList(R_keys)) { + this.R_keys = R_keys; } else { - logWarning(`Creating player "${this.name}" without a RIGHT key.`); + logWarning(`Creating player "${this.name}" without any RIGHT key(s).`); } } diff --git a/lib/Utilities.js b/lib/Utilities.js index 7ab6b52..8fd6796 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -114,3 +114,9 @@ function anyKeyBeingPressed(keyCodes) { return Array.isArray(keyCodes) && keyCodes.some(Keyboard.isDown, Keyboard); } +function isKeyList(keys) { + return Array.isArray(keys) + && keys.length > 0 + && keys.every(isPositiveInt); +} + From af3bff560d5e00ffdb834577a484d2aa975ec1cd Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Thu, 7 Apr 2016 19:43:06 +0200 Subject: [PATCH 115/168] Intercept everything, not just 'special' keys Up until this commit, only explicitly designated special keys would be intercepted and .preventDefault()ed. This does not cover multikey shortcuts such as Alt + Left Arrow, which is unacceptable because the game will be unloaded if for example Green and Yellow turn at the same time. This commit removes the notion of 'special' keys and intercepts all key events (except those outside the browser's control, such as Alt + Tab or Win). --- Zatacka.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Zatacka.js b/Zatacka.js index 256c67a..8560b57 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -23,8 +23,7 @@ const Zatacka = ((window, document) => { maxHoleLength: 12, // Kuxels keys: { "proceed": [KEY.SPACE, KEY.ENTER], - "quit": [KEY.ESCAPE], - "special": [KEY.ALT] + "quit": [KEY.ESCAPE] }, defaultPlayers: Object.freeze([ { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, @@ -44,10 +43,6 @@ const Zatacka = ((window, document) => { return config.keys.quit.indexOf(key) !== -1; } - function isSpecialKey(key) { - return config.keys.special.indexOf(key) !== -1; - } - function defaultPlayerData(id) { return config.defaultPlayers.find(defaultPlayer => defaultPlayer.id === id); } @@ -85,10 +80,8 @@ const Zatacka = ((window, document) => { } function lobbyKeyHandler(event) { + event.preventDefault(); let pressedKey = event.keyCode; - if (isSpecialKey(pressedKey)) { - event.preventDefault(); - } if (isProceedKey(pressedKey)) { proceedKeyPressedInLobby(); } else { @@ -101,10 +94,8 @@ const Zatacka = ((window, document) => { } function gameKeyHandler(event) { + event.preventDefault(); let pressedKey = event.keyCode; - if (isSpecialKey(pressedKey)) { - event.preventDefault(); - } if (isProceedKey(pressedKey)) { game.proceedKeyPressed(); } else if (isQuitKey(pressedKey)) { From 6735a4e8707e4c69ed45e053b6689b5f2b8b214d Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Thu, 7 Apr 2016 23:03:02 +0200 Subject: [PATCH 116/168] Add info/warning message HTML and CSS --- ZATACKA.html | 1 + Zatacka.css | 24 ++++++++++++++++++++++++ lib/InfoMessage.js | 5 +++++ lib/Message.js | 14 ++++++++++++++ lib/WarningMessage.js | 5 +++++ resources/kurve-icon-error.png | Bin 0 -> 2978 bytes resources/kurve-icon-info.png | Bin 0 -> 2911 bytes resources/kurve-icon-warning.png | Bin 0 -> 2906 bytes 8 files changed, 49 insertions(+) create mode 100644 lib/InfoMessage.js create mode 100644 lib/Message.js create mode 100644 lib/WarningMessage.js create mode 100644 resources/kurve-icon-error.png create mode 100644 resources/kurve-icon-info.png create mode 100644 resources/kurve-icon-warning.png diff --git a/ZATACKA.html b/ZATACKA.html index bb6f9bc..a17c8b4 100644 --- a/ZATACKA.html +++ b/ZATACKA.html @@ -45,6 +45,7 @@
+
- - + + diff --git a/Zatacka.css b/Zatacka.css index feba0ee..422218c 100644 --- a/Zatacka.css +++ b/Zatacka.css @@ -81,7 +81,7 @@ body { width: 100%; } -#lobby, #KONEC_HRY, #canvas { +#lobby, #KONEC_HRY, #canvas_main { background-color: black; overflow: hidden; } @@ -160,11 +160,11 @@ body { background-image: url("resources/kurve-icon-warning.png"); } -#canvas { +#canvas_main { z-index: 10; } -#heads { +#canvas_overlay { z-index: 12; } diff --git a/Zatacka.js b/Zatacka.js index 4b5a00f..3c6f95a 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -2,13 +2,14 @@ const Zatacka = ((window, document) => { - const canvas = byID("canvas"); + const canvas_main = byID("canvas_main"); + const canvas_overlay = byID("canvas_overlay"); const config = Object.freeze({ tickrate: 600, // Hz maxFramerate: 60, // Hz - width: canvas.width, // Kuxels - height: canvas.height, // Kuxels + width: canvas_main.width, // Kuxels + height: canvas_main.height, // Kuxels thickness: 3, // Kuxels speed: 60, // Kuxels per second turningRadius: 27, // Kuxels (NB: _radius_) @@ -227,7 +228,7 @@ const Zatacka = ((window, document) => { addLobbyEventListeners(); const guiController = GUIController(config); - const game = new Game(config, Renderer(config, canvas), guiController); + const game = new Game(config, Renderer(canvas_main, canvas_overlay), guiController); let hintProceedTimer; let hintPickTimer = setTimeout(() => { From d0245dfdfd8a0651521cd19546e33fbd27951d3e Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 9 Apr 2016 12:53:37 +0200 Subject: [PATCH 130/168] Add mouse functionality I'm not entirely satisfied with this solution, as it's kinda hacky: MOUSE is like KEY; it holds keys that map to numbers representing mouse buttons. But MOUSE.RIGHT is not 2; it is 1002. All numbers are offset by 1000 so as not to overlap the KEY key codes. Therefore, when handling a mouse event, if one wants to compare the clicked button to player configs, one must use `MOUSE.pack(event.button)` rather than `event.button` as is. --- Player.js | 4 ++-- Zatacka.js | 22 +++++++++++++++++----- lib/Utilities.js | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Player.js b/Player.js index 3d1c265..0ac2f9a 100644 --- a/Player.js +++ b/Player.js @@ -73,11 +73,11 @@ class Player { } isPressingLeft() { - return anyKeyBeingPressed(this.L_keys); + return anyInputBeingPressed(this.L_keys); } isPressingRight() { - return anyKeyBeingPressed(this.R_keys); + return anyInputBeingPressed(this.R_keys); } hasID(id) { diff --git a/Zatacka.js b/Zatacka.js index 3c6f95a..2672cc3 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -39,7 +39,7 @@ const Zatacka = ((window, document) => { { id: 3, name: "Orange", color: "#FF7900", keyL: KEY.M , keyR: KEY.COMMA }, { id: 4, name: "Green" , color: "#00CB00", keyL: KEY.LEFT_ARROW, keyR: KEY.DOWN_ARROW }, { id: 5, name: "Pink" , color: "#DF51B6", keyL: KEY.DIVIDE , keyR: KEY.MULTIPLY }, - { id: 6, name: "Blue" , color: "#00A2CB", keyL: KEY.C , keyR: KEY.V } + { id: 6, name: "Blue" , color: "#00A2CB", keyL: MOUSE.LEFT , keyR: MOUSE.RIGHT } ]) }); @@ -163,6 +163,12 @@ const Zatacka = ((window, document) => { }); } + function mouseClickedInLobby(button) { + config.defaultPlayers.forEach((playerData) => { + addOrRemovePlayer(playerData, MOUSE.pack(button)); + }); + } + function lobbyKeyHandler(event) { const pressedKey = event.keyCode; if (shouldPreventDefault(pressedKey)) { @@ -175,8 +181,9 @@ const Zatacka = ((window, document) => { } } - function lobbyMouseHandler() { - + function lobbyMouseHandler(event) { + event.preventDefault(); + mouseClickedInLobby(event.button); } function gameKeyHandler(event) { @@ -191,14 +198,15 @@ const Zatacka = ((window, document) => { } } - function gameMouseHandler() { - + function gameMouseHandler(event) { + event.preventDefault(); } function addLobbyEventListeners() { log("Adding lobby event listeners ..."); document.addEventListener("keydown", lobbyKeyHandler); document.addEventListener("mousedown", lobbyMouseHandler); + document.addEventListener("contextmenu", lobbyMouseHandler); log("Done."); } @@ -206,6 +214,7 @@ const Zatacka = ((window, document) => { log("Removing lobby event listeners ..."); document.removeEventListener("keydown", lobbyKeyHandler); document.removeEventListener("mousedown", lobbyMouseHandler); + document.removeEventListener("contextmenu", lobbyMouseHandler); log("Done."); } @@ -213,8 +222,11 @@ const Zatacka = ((window, document) => { log("Adding game event listeners ..."); document.addEventListener("keydown", Keyboard.onKeydown.bind(Keyboard)); document.addEventListener("keyup", Keyboard.onKeyup.bind(Keyboard)); + document.addEventListener("mousedown", Mouse.onMousedown.bind(Mouse)); + document.addEventListener("mouseup", Mouse.onMouseup.bind(Mouse)); document.addEventListener("keydown", gameKeyHandler); document.addEventListener("mousedown", gameMouseHandler); + document.addEventListener("contextmenu", gameMouseHandler); log("Done."); } diff --git a/lib/Utilities.js b/lib/Utilities.js index 458bb29..372b5bc 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -11,6 +11,19 @@ const typeOf = ((global) => { const KEY = Object.freeze({ BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, PAUSE: 19, CAPS_LOCK: 20, ESCAPE: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40, INSERT: 45, DELETE: 46, "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 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, LEFT_META: 91, RIGHT_META: 92, SELECT: 93, NUMPAD_0: 96, NUMPAD_1: 97, NUMPAD_2: 98, NUMPAD_3: 99, NUMPAD_4: 100, NUMPAD_5: 101, NUMPAD_6: 102, NUMPAD_7: 103, NUMPAD_8: 104, NUMPAD_9: 105, MULTIPLY: 106, ADD: 107, SUBTRACT: 109, DECIMAL: 110, DIVIDE: 111, F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117, F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123, NUM_LOCK: 144, SCROLL_LOCK: 145, SEMICOLON: 186, EQUALS: 187, COMMA: 188, DASH: 189, PERIOD: 190, FORWARD_SLASH: 191, GRAVE_ACCENT: 192, OPEN_BRACKET: 219, BACK_SLASH: 220, CLOSE_BRACKET: 221, SINGLE_QUOTE: 222 }); +const MOUSE = (() => { + const offset = 1000; + return Object.freeze({ + unpack: (offseted) => offseted - offset, + pack: (standard) => standard + offset, + LEFT: offset+0, + RIGHT: offset+2, + MIDDLE: offset+1, + MOUSE4: offset+3, + MOUSE5: offset+4 + }); +})(); + const F_KEYS = Object.freeze([ KEY.F1, KEY.F2, KEY.F3, KEY.F4, KEY.F5, KEY.F6, KEY.F7, KEY.F8, KEY.F9, KEY.F10, KEY.F11, KEY.F12 ]); @@ -134,10 +147,31 @@ const Keyboard = { } }; +const Mouse = { + pressed: {}, + isDown: function(mouseButtonNumber) { + return this.pressed[mouseButtonNumber]; + }, + onMousedown: function(event) { + this.pressed[event.button] = true; + }, + onMouseup: function(event) { + delete this.pressed[event.button]; + } +}; + function anyKeyBeingPressed(keyCodes) { return Array.isArray(keyCodes) && keyCodes.some(Keyboard.isDown, Keyboard); } +function anyMouseButtonBeingPressed(mouseButtons) { + return Array.isArray(mouseButtons) && mouseButtons.map(MOUSE.unpack).some(Mouse.isDown, Mouse); +} + +function anyInputBeingPressed(keysOrMouseButtons) { + return anyKeyBeingPressed(keysOrMouseButtons) || anyMouseButtonBeingPressed(keysOrMouseButtons); +} + function isKeyList(keys) { return Array.isArray(keys) && keys.length > 0 From 7228a79dafb5363c937d869df98fc584f310a652 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 9 Apr 2016 14:30:36 +0200 Subject: [PATCH 131/168] Add mouse warning message --- Player.js | 6 ++++++ Zatacka.js | 13 ++++++++++++- lib/Utilities.js | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Player.js b/Player.js index 0ac2f9a..673523a 100644 --- a/Player.js +++ b/Player.js @@ -84,6 +84,12 @@ class Player { return this.id === id; } + hasMouseButton(button) { + return isMouseButton(button) + && (this.L_keys.includes(button) + || this.R_keys.includes(button)); + } + hasKey(key) { return this.L_keys.includes(key) || this.R_keys.includes(key); diff --git a/Zatacka.js b/Zatacka.js index 2672cc3..92925a9 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -31,7 +31,8 @@ const Zatacka = ((window, document) => { pick: new InfoMessage(`Pick your desired color by pressing the corresponding LEFT key (e.g. M for Orange).`), proceed: new InfoMessage(`Press Space or Enter to start!`), alt: new WarningMessage(`Alt combined with some other keys (such as Tab) may cause undesired behavior (such as switching windows).`), - ctrl: new WarningMessage(`Ctrl combined with some other keys (such as W or T) may cause undesired behavior (such as closing the tab or opening a new one).`) + ctrl: new WarningMessage(`Ctrl combined with some other keys (such as W or T) may cause undesired behavior (such as closing the tab or opening a new one).`), + mouse: new WarningMessage(`Make sure to keep the mouse cursor inside the browser window; otherwise the game may lose focus and everyone may lose control.`) }), defaultPlayers: Object.freeze([ { id: 1, name: "Red" , color: "#FF2800", keyL: KEY["1"] , keyR: KEY.Q }, @@ -111,6 +112,10 @@ const Zatacka = ((window, document) => { } } + function hasMouseButton(player) { + return Object.keys(MOUSE).some((buttonName) => player.hasMouseButton(MOUSE[buttonName])); + } + function checkForDangerousKeys() { if (game.getPlayers().some((player) => player.hasKey(KEY.CTRL))) { showMessage(config.messages.ctrl); @@ -123,6 +128,12 @@ const Zatacka = ((window, document) => { } else { hideMessage(config.messages.alt); } + + if (game.getPlayers().some(hasMouseButton)) { + showMessage(config.messages.mouse); + } else { + hideMessage(config.messages.mouse); + } } function addPlayer(id) { diff --git a/lib/Utilities.js b/lib/Utilities.js index 372b5bc..7feb82f 100644 --- a/lib/Utilities.js +++ b/lib/Utilities.js @@ -172,6 +172,10 @@ function anyInputBeingPressed(keysOrMouseButtons) { return anyKeyBeingPressed(keysOrMouseButtons) || anyMouseButtonBeingPressed(keysOrMouseButtons); } +function isMouseButton(button) { + return isPositiveInt(button) && Object.keys(MOUSE).some((buttonName) => MOUSE.hasOwnProperty(buttonName)); +} + function isKeyList(keys) { return Array.isArray(keys) && keys.length > 0 From fe5005aad8351e206eb41e7b064fd2f42788b12f Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 9 Apr 2016 14:33:07 +0200 Subject: [PATCH 132/168] Rename checkForDangerousKeys to ...Input Felt this was better since we support mouse input now. --- Zatacka.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Zatacka.js b/Zatacka.js index 92925a9..b5b5917 100644 --- a/Zatacka.js +++ b/Zatacka.js @@ -116,7 +116,7 @@ const Zatacka = ((window, document) => { return Object.keys(MOUSE).some((buttonName) => player.hasMouseButton(MOUSE[buttonName])); } - function checkForDangerousKeys() { + function checkForDangerousInput() { if (game.getPlayers().some((player) => player.hasKey(KEY.CTRL))) { showMessage(config.messages.ctrl); } else { @@ -138,7 +138,7 @@ const Zatacka = ((window, document) => { function addPlayer(id) { game.addPlayer(defaultPlayer(id)); - checkForDangerousKeys(); + checkForDangerousInput(); clearTimeout(hintPickTimer); hideMessage(config.messages.pick); clearTimeout(hintProceedTimer); @@ -149,7 +149,7 @@ const Zatacka = ((window, document) => { function removePlayer(id) { game.removePlayer(id); - checkForDangerousKeys(); + checkForDangerousInput(); clearTimeout(hintProceedTimer); if (game.getNumberOfPlayers() === 0) { hideMessage(config.messages.proceed); From 44e7c9d9ec86c194cf920cbae6587065f318558a Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sat, 9 Apr 2016 15:23:09 +0200 Subject: [PATCH 133/168] Fix invisible death in hole bug Whenever a holy player crashed, nothing would be drawn to indicate where and why it died. Now, when dying, the player will occupy its last position, which makes its head appear if it dies while holy. --- Game.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Game.js b/Game.js index 40a2b93..67c1798 100644 --- a/Game.js +++ b/Game.js @@ -446,6 +446,8 @@ class Game { drawPlayer(player) { const thickness = this.config.thickness; while (player.isAlive() && !player.queuedDraws.isEmpty()) { + let color = player.getColor(); + let lastPosition = player.getLastPosition(); let currentDraw = player.queuedDraws.dequeue(); let left = this.edgeOfSquare(currentDraw.x); let top = this.edgeOfSquare(currentDraw.y); @@ -456,13 +458,15 @@ class Game { if (!this.isOnField(left, top)) { // The player wants to draw outside the playing field => DIE. this.death(player, "crashed into the wall"); + this.occupy(player, lastPosition.left, lastPosition.top); } else if (this.isCrashing(player, left, top)) { // The player wants to draw on a spot occupied by a Kurve => DIE. this.death(player, "crashed"); + this.occupy(player, lastPosition.left, lastPosition.top); } else { // The player is not dying. player.beAt(left, top); - this.Render_drawHead(left, top, player.getColor()); + this.Render_drawHead(left, top, color); if (!player.isHoly()) { // The player is not holy, so it should draw. this.occupy(player, left, top); From fedd4254b07e57f7f2a28fc646e90a615ac70e7d Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 10 Apr 2016 18:07:58 +0200 Subject: [PATCH 134/168] Move all JS files into js/ --- README.md | 4 +++- ZATACKA.html | 24 ++++++++++++------------ GUIController.js => js/GUIController.js | 0 Game.js => js/Game.js | 0 Player.js => js/Player.js | 0 Renderer.js => js/Renderer.js | 0 Round.js => js/Round.js | 0 Zatacka.js => js/Zatacka.js | 0 {lib => js/lib}/InfoMessage.js | 0 {lib => js/lib}/Message.js | 0 {lib => js/lib}/Queue.js | 0 {lib => js/lib}/Utilities.js | 0 {lib => js/lib}/WarningMessage.js | 0 {lib => js/lib}/mainloop.min.js | 0 {lib => js/lib}/mainloop.min.js.map | 0 15 files changed, 15 insertions(+), 13 deletions(-) rename GUIController.js => js/GUIController.js (100%) rename Game.js => js/Game.js (100%) rename Player.js => js/Player.js (100%) rename Renderer.js => js/Renderer.js (100%) rename Round.js => js/Round.js (100%) rename Zatacka.js => js/Zatacka.js (100%) rename {lib => js/lib}/InfoMessage.js (100%) rename {lib => js/lib}/Message.js (100%) rename {lib => js/lib}/Queue.js (100%) rename {lib => js/lib}/Utilities.js (100%) rename {lib => js/lib}/WarningMessage.js (100%) rename {lib => js/lib}/mainloop.min.js (100%) rename {lib => js/lib}/mainloop.min.js.map (100%) diff --git a/README.md b/README.md index f1c8709..22656c6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Open `ZATACKA.html` in a browser to run the game. It will automatically scale it ## Contribute +All JavaScript files can be found in `js/`. + The main JS file is `Zatacka.js`. It is responsible for creating a `Game` and supplying a config, a `Renderer` and a `GUIController` to it. It also adds `Player`s to the `Game` and forwards key events to it. `Game.js` controls the game logic. It knows which `Player`s are in the game and handles their interaction with eachother and the playing field, and it controls progress throughout the individual rounds. The `Game` constructor must be passed a config object, a `Renderer`, and a `GUIController`, respectively. @@ -20,6 +22,6 @@ The main JS file is `Zatacka.js`. It is responsible for creating a `Game` and su `GUIController.js` controls the scoreboard and additional GUI features, such as hiding the lobby and showing the results when the game ends. When a player's score is changed, the `Game` will tell its `GUIController` to update the scoreboard entry of that player. -`lib/` contains **general libraries** and help functions. +`js/lib/` contains **general libraries** and help functions. `dev/` may only contain **development tools** that are not needed at runtime, such as test suites or code generators. diff --git a/ZATACKA.html b/ZATACKA.html index 0add767..662809b 100644 --- a/ZATACKA.html +++ b/ZATACKA.html @@ -82,18 +82,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/GUIController.js b/js/GUIController.js similarity index 100% rename from GUIController.js rename to js/GUIController.js diff --git a/Game.js b/js/Game.js similarity index 100% rename from Game.js rename to js/Game.js diff --git a/Player.js b/js/Player.js similarity index 100% rename from Player.js rename to js/Player.js diff --git a/Renderer.js b/js/Renderer.js similarity index 100% rename from Renderer.js rename to js/Renderer.js diff --git a/Round.js b/js/Round.js similarity index 100% rename from Round.js rename to js/Round.js diff --git a/Zatacka.js b/js/Zatacka.js similarity index 100% rename from Zatacka.js rename to js/Zatacka.js diff --git a/lib/InfoMessage.js b/js/lib/InfoMessage.js similarity index 100% rename from lib/InfoMessage.js rename to js/lib/InfoMessage.js diff --git a/lib/Message.js b/js/lib/Message.js similarity index 100% rename from lib/Message.js rename to js/lib/Message.js diff --git a/lib/Queue.js b/js/lib/Queue.js similarity index 100% rename from lib/Queue.js rename to js/lib/Queue.js diff --git a/lib/Utilities.js b/js/lib/Utilities.js similarity index 100% rename from lib/Utilities.js rename to js/lib/Utilities.js diff --git a/lib/WarningMessage.js b/js/lib/WarningMessage.js similarity index 100% rename from lib/WarningMessage.js rename to js/lib/WarningMessage.js diff --git a/lib/mainloop.min.js b/js/lib/mainloop.min.js similarity index 100% rename from lib/mainloop.min.js rename to js/lib/mainloop.min.js diff --git a/lib/mainloop.min.js.map b/js/lib/mainloop.min.js.map similarity index 100% rename from lib/mainloop.min.js.map rename to js/lib/mainloop.min.js.map From 82272061aca049d2a667d623485d111cdd62235b Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Sun, 10 Apr 2016 18:11:07 +0200 Subject: [PATCH 135/168] Remove unnecessary word in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22656c6..26e707a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ All JavaScript files can be found in `js/`. The main JS file is `Zatacka.js`. It is responsible for creating a `Game` and supplying a config, a `Renderer` and a `GUIController` to it. It also adds `Player`s to the `Game` and forwards key events to it. -`Game.js` controls the game logic. It knows which `Player`s are in the game and handles their interaction with eachother and the playing field, and it controls progress throughout the individual rounds. The `Game` constructor must be passed a config object, a `Renderer`, and a `GUIController`, respectively. +`Game.js` controls the game logic. It knows which `Player`s are in the game and handles their interaction with eachother and the playing field, and it controls progress throughout the individual rounds. The `Game` constructor must be passed a config object, a `Renderer`, and a `GUIController`. `Player.js` describes a single player. It knows whether it is alive, where it is, where and how fast it is going, its score, and what `Game` it is attached to. From 4864f19e83e636ff9215afc22edda5cca2c9ceca Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 11 Apr 2016 11:42:04 +0200 Subject: [PATCH 136/168] Fix spawn holy bug Sometimes a player would spawn and appear to create a ridiculously long hole before starting to draw normally. I managed to pinpoint this to the player keeping its `holy` state from the last round, i.e. if a player died while holy, it would spawn holy in the next round. The `holy` property is now properly set to `false` when calling `reset()` on a `Player`. This is very imperative and not quite what I'd prefer, but it works for now. --- js/Player.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/Player.js b/js/Player.js index 673523a..024e6c0 100644 --- a/js/Player.js +++ b/js/Player.js @@ -168,6 +168,8 @@ class Player { } reset() { + this.holy = false; + this.lastPosition = null; this.queuedDraws = new Queue(); } From cef70b61311395d775ddb045da3c74c8b8998cd0 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 11 Apr 2016 13:32:59 +0200 Subject: [PATCH 137/168] Fix multiple heads bug If the hole size was large so that the lone head was clearly visible, one could clearly see that there would often be multiple overlapping heads next to each other. The reason for this was that the head of a player would be drawn for every queued draw on said player, and there can be many queued draws per refresh. Now, the head is only drawn once per refresh (when all queued draws have been processed, so always at the most recent position). --- js/Game.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/js/Game.js b/js/Game.js index 67c1798..790676f 100644 --- a/js/Game.js +++ b/js/Game.js @@ -466,7 +466,6 @@ class Game { } else { // The player is not dying. player.beAt(left, top); - this.Render_drawHead(left, top, color); if (!player.isHoly()) { // The player is not holy, so it should draw. this.occupy(player, left, top); @@ -476,6 +475,16 @@ class Game { } } + drawHead(player) { + if (player.isAlive()) { + const lastPosition = player.getLastPosition(); + const left = lastPosition.left; + const top = lastPosition.top; + const color = player.getColor(); + this.Render_drawHead(left, top, color); + } + } + updateGUIScoreboard() { const isAlive = this.constructor.isAlive; const updateScore = (player) => { @@ -587,7 +596,6 @@ class Game { * The amount of time since the last update, in seconds. */ update(delta) { - this.Render_clearHeads(); this.players.forEach((player) => { this.updatePlayer(player, delta); }); this.totalNumberOfTicks++; // Cycle players so the players take turns being prioritized: @@ -600,7 +608,9 @@ class Game { * Draws all players. */ draw() { + this.Render_clearHeads(); this.players.forEach(this.drawPlayer, this); + this.players.forEach(this.drawHead, this); } /** From 7156cccb144403db1e314686ac097f0c89491c6b Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 11 Apr 2016 23:18:18 +0200 Subject: [PATCH 138/168] Improve implementation of hole size and interval The earlier implementation would interpret a hole size of 8 as "8 Kuxels from the center of the last drawn square before the hole to the center of the first one after the hole". The problem with that was that the actual hole size would be smaller than expected: For example, with a thickness of 5 and a hole size of 8, the actual hole would only be 8 - 5 = 3 Kuxels. This commit aims to fix that by applying a padding to the desired hole sizes and intervals. The padding is (and pretty much must be) equal to the thickness of the Kurves. --- js/Player.js | 30 +++++++++++++++--------------- js/Zatacka.js | 17 ++++++++++++++--- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/js/Player.js b/js/Player.js index 024e6c0..1593c4e 100644 --- a/js/Player.js +++ b/js/Player.js @@ -50,10 +50,10 @@ class Player { static isHoleConfig(holeConfig) { return isObject(holeConfig) && arePositiveNumbers([ - holeConfig.minHoleSize, - holeConfig.maxHoleSize, - holeConfig.minHoleInterval, - holeConfig.maxHoleInterval + holeConfig.minPaddedHoleSize, + holeConfig.maxPaddedHoleSize, + holeConfig.minPaddedHoleInterval, + holeConfig.maxPaddedHoleInterval ]); } @@ -126,16 +126,16 @@ class Player { return this.direction; } - randomHoleSize() { - return randomFloat(this.holeConfig.minHoleSize, this.holeConfig.maxHoleSize); + randomPaddedHoleSize() { + return randomFloat(this.holeConfig.minPaddedHoleSize, this.holeConfig.maxPaddedHoleSize); } - randomHoleInterval() { - return randomFloat(this.holeConfig.minHoleInterval, this.holeConfig.maxHoleInterval); + randomPaddedHoleInterval() { + return randomFloat(this.holeConfig.minPaddedHoleInterval, this.holeConfig.maxPaddedHoleInterval); } firstHoleDelay() { - return distanceToDuration(this.randomHoleInterval() - this.holeConfig.minHoleInterval, this.velocity); + return distanceToDuration(this.randomPaddedHoleInterval() - this.holeConfig.minPaddedHoleInterval, this.velocity); } @@ -192,16 +192,16 @@ class Player { beginHole() { this.holy = true; - const holeSize = this.randomHoleSize(); - const holeDuration = distanceToDuration(holeSize, this.velocity); - this.holeTimer = setTimeout(this.endHole.bind(this), holeDuration); + const paddedHoleSize = this.randomPaddedHoleSize(); + const paddedHoleDuration = distanceToDuration(paddedHoleSize, this.velocity); + this.holeTimer = setTimeout(this.endHole.bind(this), paddedHoleDuration); } endHole() { this.holy = false; - const holeInterval = this.randomHoleInterval(); - const holeIntervalDuration = distanceToDuration(holeInterval, this.velocity); - this.holeTimer = setTimeout(this.beginHole.bind(this), holeIntervalDuration); + const paddedHoleInterval = this.randomPaddedHoleInterval(); + const paddedHoleIntervalDuration = distanceToDuration(paddedHoleInterval, this.velocity); + this.holeTimer = setTimeout(this.beginHole.bind(this), paddedHoleIntervalDuration); } startCreatingHoles() { diff --git a/js/Zatacka.js b/js/Zatacka.js index b5b5917..4b585bc 100644 --- a/js/Zatacka.js +++ b/js/Zatacka.js @@ -20,8 +20,8 @@ const Zatacka = ((window, document) => { flickerDuration: 830, // ms, when spawning minHoleInterval: 90, // Kuxels maxHoleInterval: 300, // Kuxels - minHoleSize: 8, // Kuxels - maxHoleSize: 12, // Kuxels + minHoleSize: 5, // Kuxels + maxHoleSize: 9, // Kuxels hintDelay: 3000, // ms keys: { "proceed": [KEY.SPACE, KEY.ENTER], @@ -84,6 +84,17 @@ const Zatacka = ((window, document) => { }; } + function getPaddedHoleConfig() { + const thickness = config.thickness; + const holeConfig = getHoleConfig(); + const paddedHoleConfig = {}; + paddedHoleConfig.minPaddedHoleSize = holeConfig.minHoleSize + thickness; + paddedHoleConfig.maxPaddedHoleSize = holeConfig.maxHoleSize + thickness; + paddedHoleConfig.minPaddedHoleInterval = Math.max(0, holeConfig.minHoleInterval - thickness); + paddedHoleConfig.maxPaddedHoleInterval = Math.max(0, holeConfig.maxHoleInterval - thickness); + return paddedHoleConfig; + } + function defaultPlayerData(id) { return config.defaultPlayers.find(defaultPlayer => defaultPlayer.id === id); } @@ -98,7 +109,7 @@ const Zatacka = ((window, document) => { playerData.color, playerData.keyL, playerData.keyR, - getHoleConfig()); + getPaddedHoleConfig()); } function proceedKeyPressedInLobby() { From cccd8550145827a4218b0f08a393f11d13bc7d3d Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Fri, 15 Apr 2016 18:03:23 +0200 Subject: [PATCH 139/168] Turning radius 27 -> 28.5 Kx --- js/Zatacka.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/Zatacka.js b/js/Zatacka.js index 4b585bc..c5187fc 100644 --- a/js/Zatacka.js +++ b/js/Zatacka.js @@ -12,7 +12,7 @@ const Zatacka = ((window, document) => { height: canvas_main.height, // Kuxels thickness: 3, // Kuxels speed: 60, // Kuxels per second - turningRadius: 27, // Kuxels (NB: _radius_) + turningRadius: 28.5, // Kuxels (NB: _radius_) minSpawnAngle: -Math.PI/2, // radians maxSpawnAngle: Math.PI/2, // radians spawnMargin: 100, // Kuxels From 6a173863af71ff9738855ff12c7d59a43f66c22e Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 12 Apr 2016 16:00:26 +0200 Subject: [PATCH 140/168] Create and use getLivePlayers() --- js/Game.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/js/Game.js b/js/Game.js index 790676f..1ffd43e 100644 --- a/js/Game.js +++ b/js/Game.js @@ -149,13 +149,17 @@ class Game { return this.players; } + getLivePlayers() { + const isAlive = this.constructor.isAlive; + return this.players.filter(isAlive); + } + getNumberOfPlayers() { return this.players.length; } getNumberOfLivePlayers() { - const isAlive = this.constructor.isAlive; - return this.players.filter(isAlive).length; + return this.getLivePlayers().length; } getScoreOfPlayer(id) { @@ -486,12 +490,11 @@ class Game { } updateGUIScoreboard() { - const isAlive = this.constructor.isAlive; const updateScore = (player) => { const id = player.getID(); this.GUI_updateScoreOfPlayer(id, this.getScoreOfPlayer(id)); } - this.players.filter(isAlive).forEach(updateScore); + this.getLivePlayers().forEach(updateScore); } death(player, cause) { From 27a8832400a50d75aabc1462b1cf0d9597d65209 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 12 Apr 2016 16:05:18 +0200 Subject: [PATCH 141/168] Move tick check and draw queueing to update() Instead of checking for every player whether a new draw should be queued, we now only check this once per tick and, if enough ticks have passed, we queue a draw for each player. --- js/Game.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/js/Game.js b/js/Game.js index 1ffd43e..0b4e844 100644 --- a/js/Game.js +++ b/js/Game.js @@ -62,7 +62,7 @@ class Game { return numberOfBytesToMaxValue(this.pixels.BYTES_PER_ELEMENT); } - computeMaxTicksBeforeDraw() { + maxTicksBetweenDraws() { return Math.max(Math.floor(this.config.tickrate/this.config.speed), 1); } @@ -587,9 +587,6 @@ class Game { const theta = player.getVelocity() * delta / 1000; player.x += theta * Math.cos(player.direction); player.y -= theta * Math.sin(player.direction); - if (this.totalNumberOfTicks % this.computeMaxTicksBeforeDraw() === 0) { - player.enqueueDraw(); - } } } @@ -600,6 +597,11 @@ class Game { */ update(delta) { this.players.forEach((player) => { this.updatePlayer(player, delta); }); + if (this.totalNumberOfTicks % this.maxTicksBetweenDraws() === 0) { + this.getLivePlayers().forEach((player) => { + player.enqueueDraw(); + }); + } this.totalNumberOfTicks++; // Cycle players so the players take turns being prioritized: if (this.isLive()) { From 8900741b6192c67d4a936c1b2d600b2661fdc940 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Mon, 18 Apr 2016 11:24:34 +0200 Subject: [PATCH 142/168] Add canvasHeight CSS class --- ZATACKA.html | 4 ++-- Zatacka.css | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ZATACKA.html b/ZATACKA.html index 662809b..fefea33 100644 --- a/ZATACKA.html +++ b/ZATACKA.html @@ -13,7 +13,7 @@
- +
@@ -63,7 +63,7 @@
-