commit 84ba857c5431348acbc3d90f57b8ceb6ec9d4103 Author: Víctor Martínez Date: Fri Aug 7 02:08:50 2020 +0200 First Commit! diff --git a/index.html b/index.html new file mode 100644 index 0000000..582843f --- /dev/null +++ b/index.html @@ -0,0 +1,38 @@ + + + + + + MineSweeper + + + + +
+
+

+ +
+
+ +
+
+ + + + + + + + diff --git a/scripts/board.js b/scripts/board.js new file mode 100644 index 0000000..15a346a --- /dev/null +++ b/scripts/board.js @@ -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); +} diff --git a/scripts/cell.js b/scripts/cell.js new file mode 100644 index 0000000..c6e2784 --- /dev/null +++ b/scripts/cell.js @@ -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; +} diff --git a/scripts/index.js b/scripts/index.js new file mode 100644 index 0000000..ed63c47 --- /dev/null +++ b/scripts/index.js @@ -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"); +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..9de6e60 --- /dev/null +++ b/style.css @@ -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; + } +} \ No newline at end of file