diff --git a/html/index.html b/html/index.html index 0088263..41c23f8 100644 --- a/html/index.html +++ b/html/index.html @@ -8,7 +8,8 @@ -

Minesweeper 99

+

Minesweeper 99

+

Now with 16 players max

@@ -21,9 +22,22 @@
-
+

Time left:

- 00:00 + --:-- +
+ +
+
+

Bombs left:

+ - +
+ +
+

Players:

+ -/- +
+

@@ -31,9 +45,12 @@

Welcome


-

+

- +
+

+ +


@@ -53,7 +70,8 @@ diff --git a/package-lock.json b/package-lock.json index 0e4be13..ac0e285 100644 --- a/package-lock.json +++ b/package-lock.json @@ -975,6 +975,12 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -1614,6 +1620,32 @@ "v8-compile-cache": "^2.0.3" } }, + "eslint-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-4.0.2.tgz", + "integrity": "sha512-EDpXor6lsjtTzZpLUn7KmXs02+nIjGcgees9BYjNkWra3jVq5vVa8IoCKgzT2M7dNNeoMBtaSG83Bd40N3poLw==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "loader-utils": "^2.0.0", + "object-hash": "^2.0.3", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + } + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -2017,6 +2049,17 @@ } } }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -2084,6 +2127,17 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -2915,6 +2969,15 @@ "minimist": "^1.2.5" } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3046,6 +3109,23 @@ "signal-exit": "^3.0.0" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -3560,6 +3640,12 @@ } } }, + "object-hash": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", + "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==", + "dev": true + }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", @@ -5335,6 +5421,12 @@ "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 86712f1..eaa911e 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "webpack-dev-server": "^3.11.0" }, "devDependencies": { + "eslint-loader": "^4.0.2", "file-loader": "^6.2.0", "ttf-loader": "^1.0.2" } diff --git a/scss/index.scss b/scss/index.scss index f6824c8..f42c469 100644 --- a/scss/index.scss +++ b/scss/index.scss @@ -3,6 +3,16 @@ src: url("../res/fonts/alarm\ clock.ttf") format("truetype"), } +@keyframes zoom { + 0% { + transform: rotate(-10deg) translate(9em, 0em) scale(1); + } + + 100% { + transform: rotate(-10deg) translate(9em, 0em) scale(1.1); + } +} + body { font-family: monospace; padding: 2em; @@ -14,6 +24,15 @@ body { text-align: center; } + h4.subheader { + text-align: center; + transform-origin: center; + color: yellow; + width: max-content; + margin: 0 auto; + animation: 0.4s linear 0s infinite alternate zoom; + } + div#main { height: 100%; width: max-content; @@ -68,13 +87,35 @@ body { div { margin-bottom: 0; - &#time { + &#stats { span { margin: 0 auto; display: block; text-align: center; - font-size: 5em; font-family: "Alarm Clock"; + + &:nth-of-type(1) { + font-size: 4em; + } + } + + div { + div { + display: inline-grid; + width: calc(50% - 0.5em); + grid-column-gap: 1em; + padding: 0; + text-align: center; + min-width: auto; + + h3 { + margin-top: 0; + } + + span { + font-size: 2em !important; + } + } } } @@ -87,23 +128,23 @@ body { font-size: 12px; div { - max-height: 23rem; + max-height: calc(14 * 1.25rem); overflow: auto; span { display: block; width: 100%; color: darkgray; - padding-bottom: 0.5em; - + padding-bottom: 0.25rem; + &.error { color: red; } - + &.alert { color: orange; } - + &.success { color: cyan; } @@ -115,11 +156,11 @@ body { } footer { - position: sticky; + position: relative; bottom: 0; - left: 1em; + // left: 1em; color: darkgray; - height: 1em; + height: 2em; background: #111111; width: 100%; padding: 1em; diff --git a/src/helpers/multiplayer.js b/src/helpers/multiplayer.js index 4281e87..875065b 100644 --- a/src/helpers/multiplayer.js +++ b/src/helpers/multiplayer.js @@ -4,26 +4,38 @@ import { CookieStorage } from "cookie-storage"; import log from "./log.js"; +import math from "../utilities/math.js"; const cookie = new CookieStorage(); class Multiplayer { - constructor() { + constructor(game) { // Keep local and external players - this.players = []; + this.players = {}; this.connected = false; - this.game; + this.game = game; - this.cookie = cookie.getItem("ms99_usr"); - if (this.cookie != null) { - log(`Using previous username ${this.cookie}, delete cookies for this site to reset.`, "alert"); - this.connect(this.cookie); + this.game.addListener("sendBomb", () => { + this.sendBomb.call(this); + }); + + this.game.addListener("gameOver", () => { + this.gameOver.call(this); + }); + + log("Game instance initialized"); + + var username = cookie.getItem("ms99_user"); + var opponents = sessionStorage.getItem("ms99_settings"); + if (username != null && opponents != null) { + log(`Using previous username ${username}, delete cookies for this site to reset.`, "alert"); + this.connect(username, opponents); } else { - this.register(); + this.register(username); } } - connect(username) { + connect(username, opponents) { // Show gamelog document.querySelectorAll("div#log")[0].style.display = "block"; @@ -40,60 +52,86 @@ class Multiplayer { this.socket.on("identify", () => { this.socket.emit("identification", { name: username, - playersMax: 2, + playersMax: opponents, gameID: "ms99" }); log(`Connected to server as ${username}`, "success"); - console.log(this.socket); this.connected = true; + + log(`Client ID: ${this.socket.id}`); + console.log(`Client ID: ${this.socket.id}`); }); // Handle player logins this.socket.on("roomUpdate", (data) => { - console.log(data); // Add any player that is not the local player and is not already added for (const id in data) { - if (id != this.socket.id && this.players.indexOf(id)) { - console.log(id); - this.players.push(id); - console.log(this.players); + if (id != this.socket.id && this.players[id] == undefined) { + this.players[id] = data[id]; + log(`${this.players[id].username} has joined`, "success"); } } // Remove any player that disconnects from the game for (const id in this.players) { if (data[id] == undefined) { - var index = this.players.indexOf(id); - this.players.splice(index, 1); - console.log(this.players); + log(`${this.players[id].username} has left.`, "alert"); + delete this.players[id]; + if (Object.keys(this.players).length == 0) { + log("No other players left", "alert"); + this.game.gameOver(); + } } } + + this.game.drawStats({ + current: Object.keys(this.players).length + 1, + max: opponents + }); }); // Handle player updates this.socket.on("update", (data) => { - console.log(data); + if (data.player.target) { + if (data.player.target.id == this.socket.id) { + this.game.addBomb(); + } + } else { + if (!data.player.active) { + log(`${this.players[data.uuid].username} had died`); + delete this.players[data.uuid]; + if (Object.keys(this.players).length == 0) { + log("No other players left", "alert"); + this.game.gameOver(); + } + } + } }); // Start game this.socket.on("gameStart", () => { - console.log("gameStart"); this.game.active = true; + log("Room is full, starting game!", "alert"); }); } - register() { + register(username = "") { var form = document.querySelectorAll("div#register > form")[0]; form.parentElement.style.display = "block"; + + form.querySelector("input[name=username]").value = username; + form.addEventListener("submit", (e) => { e.preventDefault(); form.parentElement.style.display = "none"; var username = String(form.querySelector("input[name=username]").value); + var opponents = form.querySelector("input[name=opponents]").value; if (form.querySelector("input[name=store]").checked) { - cookie.setItem("ms99_usr", username); + cookie.setItem("ms99_user", username); + sessionStorage.setItem("ms99_settings", opponents); } - this.connect(username); + this.connect(username, opponents); }); } @@ -101,9 +139,21 @@ class Multiplayer { log(error, "error"); } - setInstance(game) { - this.game = game; - log("Game instance initialized"); + sendBomb() { + var rand = math.rand(0, Object.keys(this.players).length); + + this.socket.emit("update", { + active: this.game.active, + target: { + id: Object.keys(this.players)[rand] + } + }); + } + + gameOver() { + this.socket.emit("update", { + active: this.game.active + }); } } diff --git a/src/index.js b/src/index.js index 4f781e0..0ed031c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-vars */ import "../scss/index.scss"; -import "../node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss"; -import log from "./helpers/log.js"; +// import "../node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss"; + import Game from "./screens/game.js"; import Multiplayer from "./helpers/multiplayer.js"; @@ -22,9 +22,8 @@ import Multiplayer from "./helpers/multiplayer.js"; ); var board = document.getElementById("board"); -var mp = new Multiplayer(); var game = new Game(board); -mp.setInstance(game); +var mp = new Multiplayer(game); window.setInterval(function () { game.update(); diff --git a/src/screens/game.js b/src/screens/game.js index 7744d12..57c1cba 100644 --- a/src/screens/game.js +++ b/src/screens/game.js @@ -1,8 +1,13 @@ -import * as math from "../utilities/math.js"; +import math from "../utilities/math.js"; import log from "../helpers/log.js"; +import { + EventEmitter +} from "events"; -class Game { +class Game extends EventEmitter { constructor(element) { + super(); + this.board = element; this.children = element.children; this.colours = [ @@ -28,7 +33,6 @@ class Game { this.discovered = []; this.time = 60; - this.strikes = 3; for (let cell = 0; cell < this.size; cell++) { this.bombs.push(0); @@ -48,20 +52,21 @@ class Game { this.board.appendChild(child); } + console.log(this.bombs); } update() { if (this.active) { - if (this.time - 1 <= 0) { + this.time--; + if (this.time <= 0) { this.gameOver(); } else { - this.time--; - this.drawTime(); + this.drawStats(); } } } - drawTime() { + drawStats(players = undefined) { var minutes = 0; while (this.time - 60 - (minutes * 60) >= 0) { @@ -75,10 +80,78 @@ class Game { if (seconds < 10) { seconds = String("0" + seconds); + if (this.time < 0) { + seconds = String("00"); + } } - var timer = document.getElementById("time").children[1]; + var timer = document.getElementById("stats").children[1]; timer.innerHTML = String(minutes + ":" + seconds); + + var bombCounter = document.getElementById("bombs").children[1]; + bombCounter.innerHTML = String(this.bombAmount); + + if (players != undefined) { + var playersStats = document.getElementById("players").children[1]; + playersStats.innerHTML = String(players.current + "/" + players.max); + } + } + + surrounds(cell) { + var surrounds = []; + var limit = { + top: true, + bottom: true + }; + + if (!(cell - this.sides < 0)) { + // Cell is not on top of the map + limit.top = false; + surrounds.push( + cell - this.sides + ); + } + + if (!(cell + this.sides > this.size - 1)) { + // Cell is not on the bottom of the map + limit.bottom = false; + surrounds.push( + cell + this.sides + ); + } + + if (cell % this.sides === 0) { + // Cell is on the left side of the map + surrounds.push(cell + 1); + if (!limit.top) { + surrounds.push(cell - this.sides + 1); + } + if (!limit.bottom) { + surrounds.push(cell + this.sides + 1); + } + } else if ((cell + 1) % this.sides === 0) { + // Cell is on the right side of the map + surrounds.push(cell - 1); + if (!limit.top) { + surrounds.push(cell - this.sides - 1); + } + if (!limit.bottom) { + surrounds.push(cell + this.sides - 1); + } + } else { + // Cell is not on either sides of the map + surrounds.push(cell - 1); + surrounds.push(cell + 1); + if (!limit.top) { + surrounds.push(cell - this.sides - 1); + surrounds.push(cell - this.sides + 1); + } + if (!limit.bottom) { + surrounds.push(cell + this.sides - 1); + surrounds.push(cell + this.sides + 1); + } + } + return surrounds; } discover(cell) { @@ -87,7 +160,7 @@ class Game { let index = 0; while (index < this.bombAmount) { - var rand = math.default.rand(0, this.size); + var rand = math.rand(0, this.size); if (rand != cell && !this.bombs[rand]) { this.bombs[rand] = 1; index++; @@ -104,59 +177,7 @@ class Game { return; } - var surrounds = []; - var limit = { - top: true, - bottom: true - }; - - if (!(cell - this.sides < 0)) { - // Cell is not on top of the map - limit.top = false; - surrounds.push( - cell - this.sides - ); - } - - if (!(cell + this.sides > this.size - 1)) { - // Cell is not on the bottom of the map - limit.bottom = false; - surrounds.push( - cell + this.sides - ); - } - - if (cell % this.sides === 0) { - // Cell is on the left side of the map - surrounds.push(cell + 1); - if (!limit.top) { - surrounds.push(cell - this.sides + 1); - } - if (!limit.bottom) { - surrounds.push(cell + this.sides + 1); - } - } else if ((cell + 1) % this.sides === 0) { - // Cell is on the right side of the map - surrounds.push(cell - 1); - if (!limit.top) { - surrounds.push(cell - this.sides - 1); - } - if (!limit.bottom) { - surrounds.push(cell + this.sides - 1); - } - } else { - // Cell is not on either sides of the map - surrounds.push(cell - 1); - surrounds.push(cell + 1); - if (!limit.top) { - surrounds.push(cell - this.sides - 1); - surrounds.push(cell - this.sides + 1); - } - if (!limit.bottom) { - surrounds.push(cell + this.sides - 1); - surrounds.push(cell + this.sides + 1); - } - } + var surrounds = this.surrounds(cell); var value = 0; surrounds.forEach((number) => { @@ -189,8 +210,10 @@ class Game { this.children[cell].classList.add("flag"); this.bombAmount--; + this.emit("sendBomb"); + this.time = this.time + 10; - this.drawTime(); + this.drawStats(); if (this.bombAmount == 0) { log("You have cleared all bombs!", "success"); @@ -200,7 +223,7 @@ class Game { } else { if (!this.discovered[cell]) { this.time = this.time - 30; - this.drawTime(); + this.drawStats(); this.discover(cell); } @@ -208,9 +231,26 @@ class Game { } } + addBomb() { + var cell = math.rand(0, this.bombs.length); + for (let tries = 0; tries < 10; tries++) { + var sum = this.surrounds(cell).reduce((a, b) => this.discovered[a] + this.discovered[b], 0); + if (sum == 0) { + this.bombs[cell] = 1; + this.bombAmount++; + log("A bomb has been added to your field!", "alert"); + return; + } + } + } + gameOver() { log("Game over", "error"); this.active = false; + this.drawStats(); + + this.emit("gameOver"); + for (let index = 0; index < this.size; index++) { if (this.bombs[index]) { this.children[index].innerHTML = "[]"; diff --git a/webpack.config.js b/webpack.config.js index 8dac062..8c1681c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,11 @@ module.exports = { module: { rules: [ + { + test: /\.(js)$/, + exclude: /node_modules/, + use: ["eslint-loader"] + }, { test: /\.s[ac]ss$/i, use: [