diff --git a/package.json b/package.json index b805d84..8c1a5e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minesweeper", - "version": "0.0.4", + "version": "0.0.5", "description": "Just Minesweeper!", "author": "Felix W. Dekker", "browser": "dist/bundle.js", diff --git a/src/main/js/index.js b/src/main/js/index.js index 4e64aa2..332d566 100644 --- a/src/main/js/index.js +++ b/src/main/js/index.js @@ -2,7 +2,7 @@ import {$, doAfterLoad, footer, header, nav} from "@fwdekker/template"; import {MersenneTwister19937, Random} from "random-js"; -const logArea = document.getElementById("logArea"); +const logArea = $("#logArea"); const log = (message) => { logArea.value += `${message}\n`; logArea.scrollTop = logArea.scrollHeight; @@ -17,12 +17,12 @@ class Game { * Constructs and starts a new game of Minesweeper. */ constructor() { - this.canvas = document.getElementById("canvas"); - this.settingsForm = document.getElementById("settingsForm"); - this.widthInput = document.getElementById("settingsWidth"); - this.heightInput = document.getElementById("settingsHeight"); - this.minesInput = document.getElementById("settingsMines"); - this.seedInput = document.getElementById("settingsSeed"); + this.canvas = $("#canvas"); + this.settingsForm = $("#settingsForm"); + this.widthInput = $("#settingsWidth"); + this.heightInput = $("#settingsHeight"); + this.minesInput = $("#settingsMines"); + this.seedInput = $("#settingsSeed"); this.reset(); this.display = new Display(this.canvas, this.field); @@ -116,7 +116,7 @@ class Display { constructor(canvas, field) { // TODO Remove this \/ this.frameNumber = 0; - this.counter = document.getElementById("counter"); + this.counter = $("#counter"); window.setInterval(() => { this.counter.innerText = "" + (this.frameNumber * 4); this.frameNumber = 0; @@ -198,7 +198,7 @@ class Display { ctx.textBaseline = "middle"; ctx.textAlign = "center"; this.field.cellList.forEach(cell => { - const neighborMineCount = cell.getNeighborMineCount(); + const neighborMineCount = cell.getNeighborCount(it => it.hasMine); let contents; if (cell.isCovered) { if (cell.hasFlag) @@ -260,6 +260,7 @@ class Field { constructor(width, height, mineCount, seed = undefined) { this.width = width; this.height = height; + this.mineCount = mineCount; const mines = Array(width * height).fill(true, 0, mineCount).fill(false, mineCount); shuffleArrayInPlace(mines, seed); @@ -268,6 +269,18 @@ class Field { this.cells = chunkifyArray(this.cellList, this.width); } + /** + * Returns a deep copy of this field. + * + * @return {Field} a deep copy of this field + */ + copy() { + const copy = new Field(this.width, this.height, this.mineCount, undefined); + copy.cellList = this.cellList.map(it => it.copy()); + copy.cellList.forEach(it => it.field = copy); + copy.cells = chunkifyArray(copy.cellList, copy.width); + } + /** * Returns the cell at the given coordinates, or throws an error if there is no cell there. @@ -296,16 +309,23 @@ class Field { return row === undefined ? orElse : row[y]; } + /** * Returns `true` if and only if all mineless cells have been uncovered. * * @return `true` if and only if all mineless cells have been uncovered */ isCleared() { - return this.cellList.reduce( - (isCleared, cell) => isCleared && (!cell.isCovered || cell.hasMine), - true - ); + return this.cellList.find(it => !it.hasMine && it.isCovered) !== undefined; + } + + /** + * Returns `true` if and only if a mine has been uncovered. + * + * @returns {boolean} if and only if a mine has been uncovered + */ + isFailed() { + return this.cellList.find(it => it.hasMine && !it.isCovered) !== undefined; } } @@ -331,6 +351,18 @@ class Cell { this.hasFlag = false; } + /** + * Returns a deep copy of this cell, without a reference to any field. + * + * @returns {Cell} a deep copy of this cell, without a reference to any field + */ + copy() { + const copy = new Cell(undefined, this.x, this.y, this.hasMine); + copy.isCovered = this.isCovered; + copy.hasFlag = this.hasFlag + return copy; + } + /** * Returns the `Cell`s that are adjacent to this cell. @@ -351,21 +383,13 @@ class Cell { } /** - * Returns the number of neighbors that have a flag. + * Returns the number of neighbors that satisfy the given property. * - * @returns {number} the number of neighbors that have a flag + * @param property {function} the property to check on each neighbor + * @returns {number} the number of neighbors that satisfy the given property */ - getNeighborFlagCount() { - return this.getNeighbors().filter(it => it.hasFlag).length; - } - - /** - * Returns the number of neighbors that have a mine. - * - * @returns {number} the number of neighbors that have a mine - */ - getNeighborMineCount() { - return this.getNeighbors().filter(it => it.hasMine).length; + getNeighborCount(property) { + return this.getNeighbors().filter(property).length; } @@ -375,7 +399,7 @@ class Cell { */ chord() { if (this.isCovered) return; - if (this.getNeighborMineCount() !== this.getNeighborFlagCount()) return; + if (this.getNeighborCount(it => it.hasFlag) !== this.getNeighborCount(it => it.hasMine)) return; this.getNeighbors() .filter(it => it.isCovered && !it.hasFlag) @@ -397,7 +421,7 @@ class Cell { } this.getNeighbors() - .filter(it => it.getNeighborMineCount() === 0 && !it.hasMine && !it.hasFlag) + .filter(it => it.getNeighborCount(it => it.hasMine) === 0 && !it.hasMine && !it.hasFlag) .forEach(it => it.uncover()); } @@ -418,7 +442,7 @@ class Cell { this.isCovered = false; this.hasFlag = false; - if (!this.hasMine && this.getNeighborMineCount() === 0) + if (!this.hasMine && this.getNeighborCount(it => it.hasMine) === 0) this.chord(); } } @@ -477,7 +501,7 @@ doAfterLoad(() => { // Initialize game const urlParams = new URLSearchParams(window.location.search); - document.getElementById("settingsSeed").value = + $("#settingsSeed").value = urlParams.get("seed") === null ? "" + Math.floor(Math.random() * 1000000000000) : urlParams.get("seed");