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