init commit
This commit is contained in:
commit
240d0bc213
|
|
@ -0,0 +1,116 @@
|
|||
body, html
|
||||
{
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
#game_container {
|
||||
float: left;
|
||||
|
||||
border: 1px solid #CCC;
|
||||
margin-bottom: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.row
|
||||
{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 35px;
|
||||
height: 25px;
|
||||
border: 1px solid #CCC;
|
||||
padding: 10px 5px 5px 5px;
|
||||
|
||||
background-color: #EEE;
|
||||
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
box-shadow: inset 3px 3px 5px #FFF, inset -1px -1px 2px #777;
|
||||
/* box-shadow: inset 5px 5px 10px #FFF, inset -3px -3px 5px #777;*/
|
||||
|
||||
cursor: pointer;
|
||||
transition-property: background-color, box-shadow;
|
||||
transition-duration: 1s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.cell::-moz-selection {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.cell::selection {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.cell.revealed, .cell.flagged {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.cell.revealed {
|
||||
background-color: #DDD;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.cell.adj-1 {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.cell.adj-2 {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.cell.adj-3 {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.cell.adj-4 {
|
||||
color: purple;
|
||||
}
|
||||
|
||||
.cell.adj-5 {
|
||||
color: maroon;
|
||||
}
|
||||
|
||||
.cell.adj-6 {
|
||||
color: turquoise;
|
||||
}
|
||||
|
||||
.cell.adj-7 {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.cell.adj-8 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cell.adj-M {
|
||||
font-family: "FontAwesome";
|
||||
background-color: #FFCCCC;
|
||||
}
|
||||
.cell.adj-M:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.cell.flagged {
|
||||
font-family: "FontAwesome";
|
||||
}
|
||||
.cell.flagged:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
content: "";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.status_msg {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
width: 50px;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang='en' class=''>
|
||||
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<title>Minesweeper</title>
|
||||
|
||||
<link rel="stylesheet" href="ms.css">
|
||||
<script src="ms.js"></script>
|
||||
|
||||
<script src="https://use.fontawesome.com/e51c6dea6f.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<div class="clearfix">
|
||||
<div id="game_container">
|
||||
<!--placeholder for game -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- VT commented out
|
||||
<div style="margin: 10px 0px;">
|
||||
F = Flagged, M = Mine, Left Click to reveal a cell, Right Click to flag a cell
|
||||
</div> -->
|
||||
|
||||
<div class="status_msg">
|
||||
<strong>Mines Remaining:</strong> <span id="mine_count"></span>
|
||||
</div>
|
||||
<div class="status_msg">
|
||||
<strong>Moves Made:</strong> <span id="moves_made"></span>
|
||||
</div>
|
||||
<div class="status_msg">
|
||||
<strong>Game Status:</strong> <span id="game_status"></span>
|
||||
</div>
|
||||
|
||||
<div style="margin: 10px 0px;">
|
||||
<input id="validate_button" type="button" value="Did I win?" />
|
||||
</div>
|
||||
|
||||
<div style="margin: 15px 0px;">
|
||||
<!-- VT commented out
|
||||
<div style="margin: 8px 0px;">
|
||||
<label for="new_rows">Rows:</label>
|
||||
<input type="number" id="new_rows" placeholder="rows" value="8" />
|
||||
<label for="new_cols">Cols:</label>
|
||||
<input type="number" id="new_cols" placeholder="cols" value="8" />
|
||||
<label for="new_mines">Mines:</label>
|
||||
<input type="number" id="new_mines" placeholder="mines" value="10" />
|
||||
</div>
|
||||
<div style="font-size: 10pt;">*max size: 19 x 19</div> -->
|
||||
<input id="new_game_button" type="button" value="Create new game!" />
|
||||
<input id="cheat_button" type="button" value="Cheat!" />
|
||||
</div>
|
||||
<!-- VT commented out
|
||||
<div>
|
||||
<input id="cheat_button" type="button" value="Cheat!" />
|
||||
<div style="font-size: 10pt;">*grid layout printed to JS console</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
Minesweeper.js
|
||||
Author: Brian Glaz
|
||||
|
||||
Desccription:
|
||||
Javascript implementation of the classic game, Minesweeper
|
||||
*/
|
||||
|
||||
//Minesweeper constructor to represent the game
|
||||
class Minesweeper {
|
||||
constructor(opts = {}) {
|
||||
let loadedData = {};
|
||||
|
||||
//check if a game is saved in localStorage
|
||||
if (hasLocalStorage && localStorage["minesweeper.data"]) {
|
||||
loadedData = JSON.parse(localStorage["minesweeper.data"]);
|
||||
this.loadGame = true;
|
||||
}
|
||||
|
||||
Object.assign(
|
||||
this,
|
||||
{
|
||||
grid: [], //will hold an array of Cell objects
|
||||
minesFound: 0, //number of mines correctly flagged by user
|
||||
falseMines: 0, //number of mines incorrectly flagged
|
||||
status_msg: "Playing...", //game status msg, 'Won','Lost', or 'Playing'
|
||||
playing: true,
|
||||
movesMade: 0, //keep track of the number of moves
|
||||
options: {
|
||||
rows: 8, //number of rows in the grid
|
||||
cols: 8, //number of columns in the grid
|
||||
mines: 10 //number of mines in the grid
|
||||
} },
|
||||
|
||||
{ options: opts },
|
||||
loadedData);
|
||||
|
||||
|
||||
//validate options
|
||||
let rows = this.options["rows"];
|
||||
|
||||
if (isNaN(rows)) {
|
||||
this.options["rows"] = 8;
|
||||
} else if (rows < 3) {
|
||||
this.options["rows"] = 3;
|
||||
} else if (rows > 19) {
|
||||
this.options["rows"] = 19;
|
||||
}
|
||||
|
||||
let cols = this.options["cols"];
|
||||
|
||||
if (isNaN(cols)) {
|
||||
this.options["cols"] = 8;
|
||||
} else if (cols < 3) {
|
||||
this.options["cols"] = 3;
|
||||
} else if (cols > 19) {
|
||||
this.options["cols"] = 19;
|
||||
}
|
||||
|
||||
if (isNaN(this.options["mines"])) {
|
||||
this.options["mines"] = 10;
|
||||
}
|
||||
if (this.options["mines"] < 0) {
|
||||
this.options["mines"] = 1;
|
||||
} else if (
|
||||
this.options["mines"] >
|
||||
this.options["rows"] * this.options["cols"])
|
||||
{
|
||||
this.options["mines"] = this.options["rows"] * this.options["cols"];
|
||||
}
|
||||
|
||||
if (this.loadGame) {
|
||||
this.load();
|
||||
} else {
|
||||
this.init();
|
||||
}
|
||||
this.save();
|
||||
}
|
||||
|
||||
//setup the game grid
|
||||
init() {
|
||||
//populate the grid with cells
|
||||
for (let r = 0; r < this.options["rows"]; r++) {
|
||||
this.grid[r] = [];
|
||||
for (let c = 0; c < this.options["cols"]; c++) {
|
||||
this.grid[r].push(new Cell({ xpos: c, ypos: r }));
|
||||
}
|
||||
}
|
||||
|
||||
//randomly assign mines
|
||||
let assignedMines = 0;
|
||||
while (assignedMines < this.options.mines) {
|
||||
var rowIndex = Math.floor(Math.random() * this.options.rows);
|
||||
var colIndex = Math.floor(Math.random() * this.options.cols);
|
||||
//assign and increment if cell is not already a mine
|
||||
let cell = this.grid[rowIndex][colIndex];
|
||||
if (!cell.isMine) {
|
||||
cell.isMine = true;
|
||||
cell.value = "M";
|
||||
assignedMines++;
|
||||
}
|
||||
}
|
||||
|
||||
//update cell values, check for adjacent mines
|
||||
for (let r = 0; r < this.options["rows"]; r++) {
|
||||
for (let c = 0; c < this.options["cols"]; c++) {
|
||||
//no need to update mines
|
||||
if (!this.grid[r][c].isMine) {
|
||||
let mineCount = 0,
|
||||
adjCells = this.getAdjacentCells(r, c);
|
||||
for (let i = adjCells.length; i--;) {
|
||||
if (adjCells[i].isMine) {
|
||||
mineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
this.grid[r][c].value = mineCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
//populate the grid from loaded data - need to create cell objects from raw data
|
||||
load() {
|
||||
for (let r = 0, r_len = this.grid.length; r < r_len; r++) {
|
||||
for (let c = 0, c_len = this.grid[r].length; c < c_len; c++) {
|
||||
this.grid[r][c] = new Cell(this.grid[r][c]);
|
||||
}
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
//construct the DOM representing the grid
|
||||
render() {
|
||||
const gameContainer = document.getElementById("game_container");
|
||||
|
||||
//clear old DOM
|
||||
gameContainer.innerHTML = "";
|
||||
|
||||
let content = "";
|
||||
for (let r = 0; r < this.options.rows; r++) {
|
||||
content += '<div class="row">';
|
||||
for (let c = 0; c < this.options.cols; c++) {
|
||||
let cellObj = this.grid[r][c];
|
||||
|
||||
//assign proper text and class to cells (needed when loading a game)
|
||||
let add_class = "",
|
||||
txt = "";
|
||||
if (cellObj.isFlagged) {
|
||||
add_class = "flagged";
|
||||
} else if (cellObj.isRevealed) {
|
||||
add_class = `revealed adj-${cellObj.value}`;
|
||||
txt = !cellObj.isMine ? cellObj.value || "" : "";
|
||||
}
|
||||
|
||||
content += `<div class="cell ${add_class}" data-xpos="${c}" data-ypos="${r}">${txt}</div>`;
|
||||
}
|
||||
content += "</div>";
|
||||
}
|
||||
|
||||
gameContainer.innerHTML = content;
|
||||
|
||||
//update input fields
|
||||
// VT commented out
|
||||
// document.getElementById("new_rows").value = this.options["rows"];
|
||||
// document.getElementById("new_cols").value = this.options["cols"];
|
||||
// document.getElementById("new_mines").value = this.options["mines"];
|
||||
|
||||
//setup status message
|
||||
document.getElementById("mine_count").textContent =
|
||||
this.options["mines"] - (this.falseMines + this.minesFound);
|
||||
document.getElementById("moves_made").textContent = this.movesMade;
|
||||
document.getElementById("game_status").textContent = this.status_msg;
|
||||
document.getElementById("game_status").style.color = "black";
|
||||
}
|
||||
|
||||
//returns an array of cells adjacent to the row,col passed in
|
||||
getAdjacentCells(row, col) {
|
||||
let results = [];
|
||||
for (
|
||||
let rowPos = row > 0 ? -1 : 0;
|
||||
rowPos <= (row < this.options.rows - 1 ? 1 : 0);
|
||||
rowPos++)
|
||||
{
|
||||
for (
|
||||
let colPos = col > 0 ? -1 : 0;
|
||||
colPos <= (col < this.options.cols - 1 ? 1 : 0);
|
||||
colPos++)
|
||||
{
|
||||
results.push(this.grid[row + rowPos][col + colPos]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
//reveal a cell
|
||||
revealCell(cell) {
|
||||
if (!cell.isRevealed && !cell.isFlagged && this.playing) {
|
||||
const cellElement = cell.getElement();
|
||||
|
||||
cell.isRevealed = true;
|
||||
cellElement.classList.add("revealed", `adj-${cell.value}`);
|
||||
cellElement.textContent = !cell.isMine ? cell.value || "" : "";
|
||||
|
||||
//end the game if user clicked a mine
|
||||
if (cell.isMine) {
|
||||
this.status_msg = "Sorry, you lost!";
|
||||
this.playing = false;
|
||||
document.getElementById("game_status").textContent = this.status_msg;
|
||||
document.getElementById("game_status").style.color = "#EE0000";
|
||||
} else if (!cell.isFlagged && cell.value == 0) {
|
||||
//if the clicked cell has 0 adjacent mines, we need to recurse to clear out all adjacent 0 cells
|
||||
const adjCells = this.getAdjacentCells(cell.ypos, cell.xpos);
|
||||
for (let i = 0, len = adjCells.length; i < len; i++) {
|
||||
this.revealCell(adjCells[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//flag a cell
|
||||
flagCell(cell) {
|
||||
if (!cell.isRevealed && this.playing) {
|
||||
const cellElement = cell.getElement(),
|
||||
mineCount = document.getElementById("mine_count");
|
||||
|
||||
if (!cell.isFlagged) {
|
||||
cell.isFlagged = true;
|
||||
cellElement.classList.add("flagged");
|
||||
mineCount.textContent = parseFloat(mineCount.textContent) - 1;
|
||||
if (cell.isMine) {
|
||||
this.minesFound++;
|
||||
} else {
|
||||
this.falseMines++;
|
||||
}
|
||||
} else {
|
||||
cell.isFlagged = false;
|
||||
cellElement.classList.remove("flagged");
|
||||
cellElement.textContent = "";
|
||||
mineCount.textContent = parseFloat(mineCount.textContent) + 1;
|
||||
if (cell.isMine) {
|
||||
this.minesFound--;
|
||||
} else {
|
||||
this.falseMines--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check if player has won the game
|
||||
validate() {
|
||||
const gameStatus = document.getElementById("game_status");
|
||||
|
||||
if (this.minesFound === this.options.mines && this.falseMines === 0) {
|
||||
this.status_msg = "You won!!";
|
||||
this.playing = false;
|
||||
gameStatus.textContent = this.status_msg;
|
||||
gameStatus.style.color = "#00CC00";
|
||||
} else {
|
||||
this.status_msg = "Sorry, you lost!";
|
||||
this.playing = false;
|
||||
gameStatus.textContent = this.status_msg;
|
||||
gameStatus.style.color = "#EE0000";
|
||||
}
|
||||
this.save();
|
||||
}
|
||||
|
||||
//debgugging function to print the grid to console
|
||||
gridToString() {
|
||||
let result = "";
|
||||
for (let r = 0, r_len = this.grid.length; r < r_len; r++) {
|
||||
for (let c = 0, c_len = this.grid[r].length; c < c_len; c++) {
|
||||
result += this.grid[r][c].value + " ";
|
||||
}
|
||||
result += "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//save the game object to localstorage
|
||||
save() {
|
||||
if (!hasLocalStorage) {
|
||||
return false;
|
||||
} else {
|
||||
let data = JSON.stringify(this);
|
||||
localStorage["minesweeper.data"] = data;
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
//Cell constructor to represent a cell object in the grid
|
||||
class Cell {
|
||||
constructor({
|
||||
xpos,
|
||||
ypos,
|
||||
value = 0,
|
||||
isMine = false,
|
||||
isRevealed = false,
|
||||
isFlagged = false })
|
||||
{
|
||||
Object.assign(this, {
|
||||
xpos,
|
||||
ypos,
|
||||
value, //value of a cell: number of adjacent mines, F for flagged, M for mine
|
||||
isMine,
|
||||
isRevealed,
|
||||
isFlagged });
|
||||
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return document.querySelector(
|
||||
`.cell[data-xpos="${this.xpos}"][data-ypos="${this.ypos}"]`);
|
||||
|
||||
}}
|
||||
|
||||
|
||||
//create a new game
|
||||
function newGame(opts = {}) {
|
||||
game = new Minesweeper(opts);
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
//attack click to new game button
|
||||
document.
|
||||
getElementById("new_game_button").
|
||||
addEventListener("click", function () {
|
||||
// VT commented out
|
||||
// const opts = {
|
||||
// rows: parseInt(document.getElementById("new_rows").value, 10),
|
||||
// cols: parseInt(document.getElementById("new_cols").value, 10),
|
||||
// mines: parseInt(document.getElementById("new_mines").value, 10) };
|
||||
const opts = {
|
||||
rows: 8,
|
||||
cols: 8,
|
||||
mines: 10 };
|
||||
|
||||
|
||||
if (hasLocalStorage) {
|
||||
localStorage.clear();
|
||||
}
|
||||
|
||||
newGame(opts);
|
||||
});
|
||||
|
||||
//attach click event to cells - left click to reveal
|
||||
document.
|
||||
getElementById("game_container").
|
||||
addEventListener("click", function (e) {
|
||||
const target = e.target;
|
||||
|
||||
if (target.classList.contains("cell")) {
|
||||
const cell =
|
||||
game.grid[target.getAttribute("data-ypos")][
|
||||
target.getAttribute("data-xpos")];
|
||||
|
||||
|
||||
if (!cell.isRevealed && game.playing) {
|
||||
game.movesMade++;
|
||||
document.getElementById("moves_made").textContent = game.movesMade;
|
||||
game.revealCell(cell);
|
||||
game.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//right click to flag
|
||||
document.
|
||||
getElementById("game_container").
|
||||
addEventListener("contextmenu", function (e) {
|
||||
e.preventDefault();
|
||||
const target = e.target;
|
||||
|
||||
if (target.classList.contains("cell")) {
|
||||
const cell =
|
||||
game.grid[target.getAttribute("data-ypos")][
|
||||
target.getAttribute("data-xpos")];
|
||||
|
||||
if (!cell.isRevealed && game.playing) {
|
||||
game.movesMade++;
|
||||
document.getElementById("moves_made").textContent = game.movesMade;
|
||||
game.flagCell(cell);
|
||||
game.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//attach click to cheat button
|
||||
document.getElementById("cheat_button").addEventListener("click", function () {
|
||||
console.log(game.gridToString());
|
||||
});
|
||||
|
||||
//attach click to validate button
|
||||
document.
|
||||
getElementById("validate_button").
|
||||
addEventListener("click", function () {
|
||||
game.validate();
|
||||
});
|
||||
|
||||
//create a game
|
||||
newGame();
|
||||
};
|
||||
|
||||
//global vars
|
||||
|
||||
var game;
|
||||
|
||||
//check support for local storage: credit - http://diveintohtml5.info/storage.html
|
||||
const hasLocalStorage = function () {
|
||||
try {
|
||||
return "localStorage" in window && window["localStorage"] !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"folders":
|
||||
[
|
||||
{
|
||||
"path": "./",
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"SublimeLinter.linters.pylint.args": [
|
||||
"--disable=unsupported-binary-operation"
|
||||
]
|
||||
},
|
||||
"debugger_configurations": [
|
||||
{
|
||||
"name": "Run file",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
},
|
||||
],
|
||||
// project-specific view settings
|
||||
}
|
||||
Loading…
Reference in New Issue