mirror of
https://codeberg.org/JasterV/sweet-minesweeper.git
synced 2026-04-27 02:15:46 +00:00
First Commit!
This commit is contained in:
commit
84ba857c54
5 changed files with 453 additions and 0 deletions
38
index.html
Normal file
38
index.html
Normal 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
89
scripts/board.js
Normal 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
67
scripts/cell.js
Normal 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
91
scripts/index.js
Normal 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
168
style.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue