Compare commits

...

2 Commits

Author SHA1 Message Date
2ee13bb022 Fixed bugs and addes several features
Room list on login
Restart button to prevent socket.io instance from hanging around
Disabled contextmenu on entire playing field
Added better logging for players who died
Changed some gameplay variables after player feedback
2020-11-30 22:05:23 +01:00
3a87d6a656 Added icons 2020-11-27 19:54:43 +01:00
10 changed files with 283 additions and 40 deletions

View File

@ -4,6 +4,12 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--
SVG Icons by FontAwesome, no changes have been made to the icons used (bomb, flag)
Licence: https://fontawesome.com/license
-->
<title>Minesweeper 99</title>
</head>
@ -38,7 +44,6 @@
<span>-/-</span>
</div>
</div>
<hr>
</div>
<div id="register">
@ -47,12 +52,21 @@
<label for="username">Username:</label><br>
<input type="text" name="username" placeholder="Username" required="true"><br><br>
<label for="opponents">Number of opponents:</label><br>
<input type="number" min="2" max="16" name="opponents" value="2"><br><br>
<label for="opponents">Number of players:</label><br>
<input type="number" min="2" max="16" name="opponents" value="2"><br>
<label for="store">Use these settings this session:</label>
<h3>Rooms available:</h3>
<span id="query">
<!-- Query output will be pushed here -->
</span>
<br>
<label for="store">Save this username:</label>
<input type="checkbox" name="store"><br>
<label for="store" style="color: tomato">This feature uses cookies.</label><br><br>
<label for="session">Use these settings this session:</label>
<input type="checkbox" name="session"><br>
<label for="session" style="color: red">These features use cookies.</label><br><br>
<button type="submit">Submit</button>
</form>
@ -63,6 +77,10 @@
<div>
<!-- Game log will be pushed here -->
</div>
<div>
<button id="restart" disabled>Restart</button>
<!-- <button>Toggle dark mode</button> -->
</div>
</div>
</div>
</div>
@ -70,7 +88,7 @@
<script src="/main.js"></script>
<footer>
> Coded by <a href="//arnweb.nl" target="new">McArn</a>.<br>
> Coded by <a href="//arnweb.nl" target="new">McArn</a>. (v1.0.7)<br>
> Found bugs? <a href="//arnweb.nl/contact/">Contact me</a>
</footer>

13
package-lock.json generated
View File

@ -511,6 +511,14 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
},
"axios": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz",
"integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@ -5136,6 +5144,11 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
},
"striptags": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz",
"integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0="
},
"style-loader": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz",

View File

@ -16,6 +16,7 @@
"license": "GPL-3.0-or-later",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"axios": "^0.21.0",
"cookie-storage": "^6.1.0",
"css-loader": "^5.0.1",
"eslint": "^7.12.1",
@ -25,6 +26,7 @@
"sass": "^1.29.0",
"sass-loader": "^10.0.5",
"socket.io-client": "^3.0.0",
"striptags": "^3.1.1",
"style-loader": "^2.0.0",
"webpack": "^5.4.0",
"webpack-cli": "^4.2.0",

1
res/svg/bomb-solid.svg Normal file
View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bomb" class="svg-inline--fa fa-bomb fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M440.5 88.5l-52 52L415 167c9.4 9.4 9.4 24.6 0 33.9l-17.4 17.4c11.8 26.1 18.4 55.1 18.4 85.6 0 114.9-93.1 208-208 208S0 418.9 0 304 93.1 96 208 96c30.5 0 59.5 6.6 85.6 18.4L311 97c9.4-9.4 24.6-9.4 33.9 0l26.5 26.5 52-52 17.1 17zM500 60h-24c-6.6 0-12 5.4-12 12s5.4 12 12 12h24c6.6 0 12-5.4 12-12s-5.4-12-12-12zM440 0c-6.6 0-12 5.4-12 12v24c0 6.6 5.4 12 12 12s12-5.4 12-12V12c0-6.6-5.4-12-12-12zm33.9 55l17-17c4.7-4.7 4.7-12.3 0-17-4.7-4.7-12.3-4.7-17 0l-17 17c-4.7 4.7-4.7 12.3 0 17 4.8 4.7 12.4 4.7 17 0zm-67.8 0c4.7 4.7 12.3 4.7 17 0 4.7-4.7 4.7-12.3 0-17l-17-17c-4.7-4.7-12.3-4.7-17 0-4.7 4.7-4.7 12.3 0 17l17 17zm67.8 34c-4.7-4.7-12.3-4.7-17 0-4.7 4.7-4.7 12.3 0 17l17 17c4.7 4.7 12.3 4.7 17 0 4.7-4.7 4.7-12.3 0-17l-17-17zM112 272c0-35.3 28.7-64 64-64 8.8 0 16-7.2 16-16s-7.2-16-16-16c-52.9 0-96 43.1-96 96 0 8.8 7.2 16 16 16s16-7.2 16-16z"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
res/svg/flag-solid.svg Normal file
View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="flag" class="svg-inline--fa fa-flag fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M349.565 98.783C295.978 98.783 251.721 64 184.348 64c-24.955 0-47.309 4.384-68.045 12.013a55.947 55.947 0 0 0 3.586-23.562C118.117 24.015 94.806 1.206 66.338.048 34.345-1.254 8 24.296 8 56c0 19.026 9.497 35.825 24 45.945V488c0 13.255 10.745 24 24 24h16c13.255 0 24-10.745 24-24v-94.4c28.311-12.064 63.582-22.122 114.435-22.122 53.588 0 97.844 34.783 165.217 34.783 48.169 0 86.667-16.294 122.505-40.858C506.84 359.452 512 349.571 512 339.045v-243.1c0-23.393-24.269-38.87-45.485-29.016-34.338 15.948-76.454 31.854-116.95 31.854z"></path></svg>

After

Width:  |  Height:  |  Size: 755 B

View File

@ -22,6 +22,14 @@ body {
h1 {
text-align: center;
img {
fill: white;
filter: invert(1);
height: 1em;
padding-right: 0.3em;
margin-bottom: -0.15em;
}
}
h4.subheader {
@ -41,6 +49,10 @@ body {
column-gap: 1em;
margin: 0 auto;
h2 {
margin-top: 0;
}
div {
padding: 1em;
margin-bottom: 1em;
@ -66,16 +78,26 @@ body {
background-color: #303030;
font-size: large;
img {
fill: white;
filter: invert(1);
height: 1em;
}
&.discovered {
background-color: #424242;
}
&.bomb {
background-color: #FC5130;
background-color: #e85354;
}
&.added {
background-color: #e89759;
}
&.flag {
background-color: #51CB20;
background-color: #4be3e3;
}
}
}
@ -121,11 +143,19 @@ body {
&#register {
display: none;
span#query {
height: 6rem;
display: block;
overflow: auto;
}
}
&#log {
display: none;
font-size: 12px;
position: relative;
min-height: 24rem;
div {
max-height: calc(14 * 1.25rem);
@ -150,6 +180,12 @@ body {
}
}
}
div:nth-of-type(2) {
position: absolute;
bottom: 0;
min-width: unset;
}
}
}
}

View File

@ -3,11 +3,19 @@ import io from "socket.io-client";
import {
CookieStorage
} from "cookie-storage";
import striptags from "striptags";
import axios from "axios";
import log from "./log.js";
import math from "../utilities/math.js";
const cookie = new CookieStorage();
// http://localhost:3000
// http://localhost:2428/tanks
const APIUrl = "https://api.arnweb.nl/tanks";
const GameID = "ms99";
class Multiplayer {
constructor(game) {
// Keep local and external players
@ -15,20 +23,26 @@ class Multiplayer {
this.connected = false;
this.game = game;
this.lastPlaying;
this.game.addListener("sendBomb", () => {
this.sendBomb.call(this);
});
this.game.addListener("cleared", () => {
this.cleared.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");
var username = cookie.getItem(`${GameID}_user`);
var opponents = sessionStorage.getItem(`${GameID}_settings`);
if (username != null && opponents != null) {
log(`Using previous username ${username}, delete cookies for this site to reset.`, "alert");
log(`Using previous username ${username} and settings, close this session to reset this.`, "alert");
this.connect(username, opponents);
} else {
this.register(username);
@ -41,7 +55,7 @@ class Multiplayer {
// Connect to TanksJS-Server instance
log("Connecting to server...");
this.socket = io("http://localhost:3000/", {
this.socket = io(APIUrl, {
reconnection: false
});
@ -53,7 +67,7 @@ class Multiplayer {
this.socket.emit("identification", {
name: username,
playersMax: opponents,
gameID: "ms99"
gameID: GameID
});
log(`Connected to server as ${username}`, "success");
this.connected = true;
@ -77,35 +91,56 @@ class Multiplayer {
if (data[id] == undefined) {
log(`${this.players[id].username} has left.`, "alert");
delete this.players[id];
if (Object.keys(this.players).length == 0) {
if (Object.keys(this.players).length == 0 && this.game.active) {
log("No other players left", "alert");
this.game.gameOver();
this.game.gameOver(true);
}
}
}
this.opponents = opponents;
this.game.drawStats({
current: Object.keys(this.players).length + 1,
current: Object.keys(this.players).length,
max: opponents
});
});
// Handle player updates
this.socket.on("update", (data) => {
if (data.player.cleared) {
this.game.gameOver();
}
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`);
log(`${this.players[data.uuid].username} has died`);
if (Object.keys(this.players).length == 1) {
this.lastPlaying = this.players[Object.keys(this.players)[0]];
}
delete this.players[data.uuid];
if (Object.keys(this.players).length == 0) {
if (this.game.active) {
log("No other players left", "alert");
this.game.gameOver();
this.game.gameOver(true);
} else {
log(`${this.lastPlaying.username} won`);
}
}
}
}
this.game.drawStats({
current: Object.keys(this.players).length,
max: opponents
});
});
// Start game
@ -119,18 +154,84 @@ class Multiplayer {
var form = document.querySelectorAll("div#register > form")[0];
form.parentElement.style.display = "block";
axios.get(APIUrl).then((res) => {
var output = document.getElementById("query");
if (res.status == 200) {
var result = res.data.players;
var span = document.createElement("span");
if (Object.keys(result).length == 0) {
span.style.color = "orange";
span.innerHTML = "No players are online :(";
output.appendChild(span);
} else {
var rooms = [];
var counts = {};
for (var player in result) {
var room = result[player].room.split("_");
if (room[0] == GameID) {
rooms.push(room[1]);
}
}
for (var i = 0; i < rooms.length; i++) {
var num = rooms[i];
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
for (const size in counts) {
span.innerHTML += String(`${counts[size]} player(s) in groups of ${size}.<br>`);
}
output.appendChild(span);
}
} else {
span.style.color = "red";
span.innerHTML = "Server is offline!";
output.appendChild(span);
form.querySelector("button").disabled = true;
}
});
var checks = {
session: form.querySelector("input[name=session]"),
store: form.querySelector("input[name=store]"),
};
form.querySelector("input[name=username]").value = username;
if (username != "") {
checks.store.checked = true;
}
checks.session.addEventListener("change", function () {
if (this.checked) {
checks.store.checked = true;
}
});
checks.store.addEventListener("change", function () {
if (!this.checked) {
checks.session.checked = false;
}
});
form.addEventListener("submit", (e) => {
e.preventDefault();
form.parentElement.style.display = "none";
var username = String(form.querySelector("input[name=username]").value);
var username = String(striptags(form.querySelector("input[name=username]").value));
var opponents = form.querySelector("input[name=opponents]").value;
if (form.querySelector("input[name=store]").checked) {
cookie.setItem("ms99_user", username);
sessionStorage.setItem("ms99_settings", opponents);
cookie.setItem(`${GameID}_user`, username);
}
if (form.querySelector("input[name=session]").checked) {
cookie.setItem(`${GameID}_user`, username);
sessionStorage.setItem(`${GameID}_settings`, opponents);
}
this.connect(username, opponents);
});
}
@ -150,10 +251,28 @@ class Multiplayer {
});
}
cleared() {
this.socket.emit("update", {
cleared: true
});
}
gameOver() {
this.socket.emit("update", {
active: this.game.active
});
this.game.drawStats({
current: Object.keys(this.players).length,
max: this.opponents
});
var button = document.getElementById("restart");
button.disabled = false;
button.addEventListener("click", (e) => {
this.socket.disconnect();
window.location.reload();
});
}
}

View File

@ -1,11 +1,10 @@
/* eslint-disable no-unused-vars */
import "../scss/index.scss";
// import "../node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
import bomb from "../res/svg/bomb-solid.svg";
import Game from "./screens/game.js";
import Multiplayer from "./helpers/multiplayer.js";
["DOMContentLoaded", "resize"].forEach(evt =>
window.addEventListener(evt, (e) => {
var main = document.getElementById("main");
@ -21,7 +20,15 @@ import Multiplayer from "./helpers/multiplayer.js";
})
);
var img = document.createElement("img");
img.src = bomb;
document.querySelector("body > h1").prepend(img);
var board = document.getElementById("board");
board.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
var game = new Game(board);
var mp = new Multiplayer(game);

View File

@ -4,6 +4,9 @@ import {
EventEmitter
} from "events";
import bomb from "../../res/svg/bomb-solid.svg";
import flag from "../../res/svg/flag-solid.svg";
class Game extends EventEmitter {
constructor(element) {
super();
@ -28,11 +31,13 @@ class Game extends EventEmitter {
this.board.style.gridTemplateColumns = `repeat(${this.sides}, auto)`;
this.generated = false;
this.bombAmount = 60;
this.bombAmount = 50;
this.bombs = [];
this.added = [];
this.discovered = [];
this.time = 60;
this.penalty = 20;
for (let cell = 0; cell < this.size; cell++) {
this.bombs.push(0);
@ -52,7 +57,6 @@ class Game extends EventEmitter {
this.board.appendChild(child);
}
console.log(this.bombs);
}
update() {
@ -93,6 +97,9 @@ class Game extends EventEmitter {
if (players != undefined) {
var playersStats = document.getElementById("players").children[1];
if (this.active || !this.generated) {
players.current++;
}
playersStats.innerHTML = String(players.current + "/" + players.max);
}
}
@ -161,7 +168,7 @@ class Game extends EventEmitter {
let index = 0;
while (index < this.bombAmount) {
var rand = math.rand(0, this.size);
if (rand != cell && !this.bombs[rand]) {
if (rand != cell && !this.bombs[rand] && this.surrounds(cell).indexOf(rand) == -1) {
this.bombs[rand] = 1;
index++;
}
@ -169,14 +176,14 @@ class Game extends EventEmitter {
}
if (this.active && !this.discovered[cell]) {
this.discovered[cell] = 1;
this.children[cell].classList.add("discovered");
if (this.bombs[cell]) {
this.gameOver();
return;
}
this.discovered[cell] = 1;
this.children[cell].classList.add("discovered");
var surrounds = this.surrounds(cell);
var value = 0;
@ -206,8 +213,12 @@ class Game extends EventEmitter {
if (this.bombs[cell]) {
if (!this.discovered[cell]) {
this.discovered[cell] = 1;
this.children[cell].innerHTML = "#";
var img = document.createElement("img");
img.src = flag;
this.children[cell].appendChild(img);
this.children[cell].classList.add("flag");
this.bombAmount--;
this.emit("sendBomb");
@ -217,12 +228,12 @@ class Game extends EventEmitter {
if (this.bombAmount == 0) {
log("You have cleared all bombs!", "success");
this.active = false;
this.emit("cleared");
}
}
} else {
if (!this.discovered[cell]) {
this.time = this.time - 30;
this.time = this.time - this.penalty;
this.drawStats();
this.discover(cell);
@ -232,20 +243,38 @@ class Game extends EventEmitter {
}
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) {
var sum = 0;
var cell = math.rand(0, this.bombs.length);
// var sum = this.surrounds(cell).reduce((a, b) => {
// console.log(this.discovered[a]);
// return this.discovered[a] + this.discovered[b];
// }, 0);
this.surrounds(cell).forEach((e) => {
sum = sum + this.discovered[e];
});
if (sum == 0 && !this.bombs[cell] && !this.discovered[cell]) {
this.bombs[cell] = 1;
this.bombAmount++;
this.added.push(cell);
// this.children[cell].style.background = "#0000ff";
log("A bomb has been added to your field!", "alert");
return;
}
}
}
gameOver() {
gameOver(win = false) {
log("Game over", "error");
if (win) {
log("Congratulations, you win!", "success");
}
this.active = false;
this.drawStats();
@ -253,8 +282,15 @@ class Game extends EventEmitter {
for (let index = 0; index < this.size; index++) {
if (this.bombs[index]) {
this.children[index].innerHTML = "[]";
if (!this.discovered[index]) {
var img = document.createElement("img");
img.src = bomb;
this.children[index].appendChild(img);
this.children[index].classList.add("bomb");
if (this.added.indexOf(index) != -1) {
this.children[index].classList.add("added");
}
}
}
}
}

View File

@ -18,7 +18,7 @@ module.exports = {
],
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: "file-loader",
options: {
@ -26,6 +26,16 @@ module.exports = {
outputPath: "res/fonts/"
}
}]
},
{
test: /\.(svg)$/,
use: [{
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "res/svg/"
}
}]
}
]
}