First Commit!

This commit is contained in:
Víctor Martínez 2020-08-07 02:08:50 +02:00
commit 84ba857c54
5 changed files with 453 additions and 0 deletions

38
index.html Normal file
View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MineSweeper</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="overlay hide">
<div class="game-over-panel">
<h2 id="panel-message"></h2>
<button id="play-again-btn">
Play Again
</button>
</div>
</div>
<div id="mines-grid" class="mines-grid">
</div>
<audio id="pop-sound">
<source src="https://cdn.glitch.com/03170861-9fec-4c05-9f36-608f4a64ba40%2Fzapsplat_cartoon_bubble_pop_005_40277.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<audio id="congrats-sound">
<source src="https://actions.google.com/sounds/v1/cartoon/wood_plank_flicks.ogg" type="audio/ogg">
Your browser does not support the audio element.
</audio>
<audio id="fail-sound">
<source src="https://cdn.glitch.com/03170861-9fec-4c05-9f36-608f4a64ba40%2Fzapsplat_cartoon_fail_negative_descending_musical_tuba_marimba_oboe_18126.mp3?v=1596757084107" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<script type="module" src="./scripts/index.js"></script>
</body>
</html>

89
scripts/board.js Normal file
View file

@ -0,0 +1,89 @@
import { createCell, generateNRandomCells } from "./cell.js";
export function createBoard(size, numMines) {
let board = [];
const mines = generateNRandomCells(numMines, 0, size);
for (let row = 0; row < size; row++) {
board.push([]);
for (let col = 0; col < size; col++) {
let newCell = createCell(row, col);
let neighbourMines = countNeighbourMines(mines, newCell);
newCell.type = neighbourMines;
board[row].push(newCell);
}
}
mines.forEach(mine => (board[mine.row][mine.col].type = "mine"));
return board;
}
export function showAllBoardMines(board) {
board.forEach(row =>
row.forEach(cell => {
if (isMine(cell)) {
cell.discover();
}
})
);
}
export function discoverNeighbours(board, cell) {
let queue = [];
let visiteds = [];
queue.push(cell);
while (queue.length > 0) {
let currentCell = queue.shift();
if (!containsCell(visiteds, currentCell) && currentCell.type == 0) {
visiteds.push(currentCell);
let validNeighbours = getValidNeighbours(board, currentCell);
for (let neighbour of validNeighbours) {
neighbour.discover();
queue.push(neighbour);
}
}
}
}
export function isMine(cell) {
return cell.type === "mine";
}
export function countDiscoveredCells(board) {
return board.reduce((result, row) => result + row.reduce((total, cell) => cell.isHidden() ? total : total + 1, 0), 0);
}
function containsCell(arr, cell) {
return arr.some(other => other.row === cell.row && other.col === cell.col);
}
function validCoords(board, row, col) {
return row >= 0 && row < board.length && col >= 0 && col < board.length;
}
function getValidNeighbours(board, cell) {
let result = [];
for (let i = cell.row - 1; i <= cell.row + 1; i++) {
for (let j = cell.col - 1; j <= cell.col + 1; j++) {
if (validCoords(board, i, j)) {
let neighbour = board[i][j];
if (neighbour.isHidden() && cell.manhattanDistance(neighbour) > 0) {
result.push(neighbour);
}
}
}
}
return result;
}
function countNeighbourMines(mines, cell) {
return mines.reduce((total, current) => {
if (current.perpendicularTo(cell)) {
return current.manhattanDistance(cell) === 1 ? total + 1 : total;
} else {
return current.manhattanDistance(cell) === 2 ? total + 1 : total;
}
}, 0);
}

67
scripts/cell.js Normal file
View file

@ -0,0 +1,67 @@
export function createCell(row, col) {
let cellContainer = document.createElement("div");
cellContainer.classList.add("cell");
return {
row,
col,
get type() {
return cellContainer.getAttribute("data-celltype");
},
set type(newContent) {
cellContainer.setAttribute("data-celltype", newContent);
},
get asHtmlElement() {
return cellContainer;
},
hide() {
cellContainer.classList.remove("discovered");
},
discover() {
cellContainer.classList.add("discovered");
cellContainer.classList.remove("flag-cell");
},
isHidden() {
return !cellContainer.classList.contains("discovered");
},
toggleFlag() {
cellContainer.classList.toggle("flag-cell");
},
manhattanDistance(other) {
return Math.abs(row - other.row) + Math.abs(col - other.col);
},
perpendicularTo(other) {
return row === other.row || col === other.col;
}
};
}
export function generateNRandomCells(n, min, max) {
let cells = [];
for (let i = 0; i < n; i++) {
let newCell = generateRandomLocatedCell(min, max);
while (cells.some(cell => cell.manhattanDistance(newCell) === 0)) {
newCell = generateRandomLocatedCell(min, max);
}
cells.push(newCell);
}
return cells;
}
export function generateRandomLocatedCell(min, max) {
return createCell(randomRange(min, max), randomRange(min, max));
}
function randomRange(min, max) {
return Math.floor(Math.random() * max) + min;
}

91
scripts/index.js Normal file
View file

@ -0,0 +1,91 @@
import {
createBoard,
isMine,
discoverNeighbours,
showAllBoardMines,
countDiscoveredCells
} from "./board.js";
const BOARD_SIZE = 10;
const BOARD_CELLS = BOARD_SIZE ** 2;
const NUM_MINES = 13;
const grid = document.getElementById("mines-grid");
let overlay = document.querySelector(".overlay");
let playAgainBtn = document.getElementById("play-again-btn");
// SOUNDS
let popSound = document.getElementById("pop-sound");
let congratsSound = document.getElementById("congrats-sound");
let failSound = document.getElementById("fail-sound");
/**----------------------------------------- */
/**------------ EVENT LISTENERS ------------ */
/**----------------------------------------- */
playAgainBtn.addEventListener("click", () => {
startNewGame();
hideOverlayPanel();
});
startNewGame();
// GAME FUNCTIONS
function startNewGame() {
let board = createBoard(BOARD_SIZE, NUM_MINES);
addGameRules(board);
displayBoard(board, BOARD_SIZE);
}
function addGameRules(board) {
board.forEach(row =>
row.forEach(cell => {
cell.asHtmlElement.addEventListener("click", () => {
if (isMine(cell)) {
failSound.play();
showAllBoardMines(board);
displayGameOverPanel();
} else {
if (cell.type == 0 && cell.isHidden()) {
congratsSound.play();
cell.discover();
discoverNeighbours(board, cell);
} else {
popSound.play();
cell.discover();
if (countDiscoveredCells(board) === BOARD_CELLS - NUM_MINES) {
displayGameWonPanel();
}
}
}
});
// EVENT LISTENER FOR RIGHT CLICK
cell.asHtmlElement.addEventListener("contextmenu", e => {
e.preventDefault();
if (cell.isHidden()) cell.toggleFlag();
});
})
);
}
function displayBoard(board, size) {
grid.innerHTML = "";
grid.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
grid.style.gridTemplateRows = `repeat(${size}, 1fr)`;
board.forEach(row => row.forEach(cell => grid.append(cell.asHtmlElement)));
}
function hideOverlayPanel() {
overlay.classList.add("hide");
}
function displayGameWonPanel() {
document.getElementById("panel-message").textContent = "You Win!! Congrats!!";
overlay.classList.remove("hide");
}
function displayGameOverPanel() {
document.getElementById("panel-message").textContent =
"You loose, try again!";
overlay.classList.remove("hide");
}

168
style.css Normal file
View file

@ -0,0 +1,168 @@
@import url("https://fonts.googleapis.com/css2?family=Montserrat+Alternates:wght@400;500;700&display=swap");
html {
background-color: #838383;
font-family: "Montserrat Alternates", sans-serif;
}
html,
body {
padding: 0;
margin: 0;
min-height: 100vh;
}
body {
display: flex;
align-items: center;
justify-content: center;
}
button {
background-color: #799351;
color: white;
border: none;
font-weight: bold;
padding: 1em 1.2em;
border-radius: 10px;
font-size: 1.5rem;
}
button:hover {
opacity: 0.9;
}
.hide {
display: none !important;
}
.mines-grid {
display: grid;
min-width: 800px;
min-height: 800px;
grid-gap: 5px;
}
.cell {
background-color: #a8df65;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
font-size: 2rem;
transition: all 0.5s;
border-radius: 10px;
color: #6a2c70;
}
.cell:hover {
background-color: #edf492;
}
.discovered {
background-color: #faf0af;
}
.discovered:hover {
background-color: #faf0af;
}
.discovered[data-celltype="2"]::before {
color: #799351;
content: "2";
}
.discovered[data-celltype="1"]::before {
color: #3b6978;
content: "1";
}
.discovered[data-celltype="3"]::before {
color: #d54062;
content: "3";
}
.discovered[data-celltype="4"]::before {
content: "4";
}
.discovered[data-celltype="5"]::before {
content: "5";
}
.discovered[data-celltype="6"]::before {
content: "6";
}
.discovered[data-celltype="7"]::before {
content: "7";
}
.discovered[data-celltype="8"]::before {
content: "8";
}
.discovered[data-celltype="mine"]::before {
content: "💣";
}
.flag-cell {
position: relative;
}
.flag-cell::before {
position: absolute;
content: "⛳️";
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.overlay {
position: fixed;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.6);
z-index: 99999;
}
.game-over-panel {
position: relative;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: transparent;
width: 400px;
color: white;
line-height: 3rem;
font-size: 2rem;
}
@media (max-width: 800px) {
.mines-grid {
min-width: 600px;
min-height: 600px;
}
}
@media (max-width: 600px) {
.mines-grid {
min-width: 450px;
min-height: 450px;
grid-gap: 3px;
}
.cell {
border-radius: 5px;
font-size: 1.2rem;
}
}
@media (max-width: 450px) {
.mines-grid {
min-width: 350px;
min-height: 350px;
}
}