Multiplayer is now working correctly

This commit is contained in:
Arne van Iterson 2020-11-27 19:06:50 +01:00
parent 3a739e33a9
commit 0fed261ac4
8 changed files with 357 additions and 111 deletions

View File

@ -8,7 +8,8 @@
</head>
<body>
<h1><i class="fas fa-bomb"></i>Minesweeper 99</h1>
<h1>Minesweeper 99</h1>
<h4 class="subheader">Now with 16 players max</h4>
<div id="main">
<div>
@ -21,9 +22,22 @@
<div>
<div id="time">
<div id="stats">
<h2>Time left:</h2>
<span>00:00</span>
<span>--:--</span>
<hr>
<div>
<div id="bombs">
<h3>Bombs left:</h3>
<span>-</span>
</div>
<div id="players">
<h3>Players:</h3>
<span>-/-</span>
</div>
</div>
<hr>
</div>
@ -31,9 +45,12 @@
<h2>Welcome</h2>
<form action="#">
<label for="username">Username:</label><br>
<input type="text" name="username" placeholder="Username"><br><br>
<input type="text" name="username" placeholder="Username" required="true"><br><br>
<label for="store">Use this username every game:</label>
<label for="opponents">Number of opponents:</label><br>
<input type="number" min="2" max="16" name="opponents" value="2"><br><br>
<label for="store">Use these settings this session:</label>
<input type="checkbox" name="store"><br>
<label for="store" style="color: tomato">This feature uses cookies.</label><br><br>
@ -53,7 +70,8 @@
<script src="/main.js"></script>
<footer>
> Coded by <a href="//arnweb.nl" target="new">McArn</a>.
> Coded by <a href="//arnweb.nl" target="new">McArn</a>.<br>
> Found bugs? <a href="//arnweb.nl/contact/">Contact me</a>
</footer>
</body>

92
package-lock.json generated
View File

@ -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",

View File

@ -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"
}

View File

@ -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;

View File

@ -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
});
}
}

View File

@ -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();

View File

@ -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 = "[]";

View File

@ -1,6 +1,11 @@
module.exports = {
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ["eslint-loader"]
},
{
test: /\.s[ac]ss$/i,
use: [