Initial commit
This commit is contained in:
commit
3ed4ec9156
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Eduardo Zardo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,2 @@
|
|||
# Minesweeper
|
||||
A minesweeper game made with pure HTML, CSS and Javascript
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="red" stroke="red" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-flag"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line></svg>
|
After Width: | Height: | Size: 324 B |
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="style.css">
|
||||
|
||||
<title>Minesweeper</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="difficulty-container">
|
||||
<label for="difficulty">Difficulty:</label>
|
||||
<select name="difficulty" id="difficulty">
|
||||
<option value="easy" selected>Easy</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="hard">Hard</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="board-container">
|
||||
<div id="board-header" class="hide">
|
||||
<div id="flags-count-container" class="hide">
|
||||
<span id="flags-count">0</span>
|
||||
<img src="assets/icons/flag.svg">
|
||||
</div>
|
||||
|
||||
<div id="result-container" class="hide">
|
||||
<span id="result"></span>
|
||||
</div>
|
||||
|
||||
<div id="timer-container" class="hide">
|
||||
<p id="timer">00:00</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="board" class="hide"></div>
|
||||
</div>
|
||||
|
||||
<button type="button" id="start-game">Start</button>
|
||||
</div>
|
||||
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,384 @@
|
|||
const difficulties = {
|
||||
'easy': {
|
||||
'size': [10, 10],
|
||||
'mines': 10,
|
||||
},
|
||||
'medium': {
|
||||
'size': [16, 16],
|
||||
'mines': 40,
|
||||
},
|
||||
'hard': {
|
||||
'size': [16, 30],
|
||||
'mines': 99,
|
||||
},
|
||||
};
|
||||
|
||||
const squareClassNames = {
|
||||
'.': 'unopened',
|
||||
'B': 'unopened',
|
||||
'F': 'unopened flag',
|
||||
'BF': 'unopened flag',
|
||||
'X': 'bomb',
|
||||
'0': 'opened empty',
|
||||
'1': 'opened one',
|
||||
'2': 'opened two',
|
||||
'3': 'opened three',
|
||||
'4': 'opened four',
|
||||
'5': 'opened five',
|
||||
'6': 'opened six',
|
||||
'7': 'opened seven',
|
||||
'8': 'opened eight',
|
||||
};
|
||||
|
||||
const squareTexts = {
|
||||
'.': '',
|
||||
'B': '',
|
||||
'F': '<img src="assets/icons/flag.svg">',
|
||||
'BF': '<img src="assets/icons/flag.svg">',
|
||||
'X': '<img src="assets/icons/mine.png">',
|
||||
'0': '',
|
||||
'1': '1',
|
||||
'2': '2',
|
||||
'3': '3',
|
||||
'4': '4',
|
||||
'5': '5',
|
||||
'6': '6',
|
||||
'7': '7',
|
||||
'8': '8',
|
||||
};
|
||||
|
||||
const directions = [
|
||||
[-1, -1],
|
||||
[-1, 0],
|
||||
[-1, 1],
|
||||
[0, -1],
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[1, -1],
|
||||
[1, 0],
|
||||
[1, 1]
|
||||
];
|
||||
|
||||
let board;
|
||||
let size;
|
||||
let mines;
|
||||
let flagCount;
|
||||
let timeInSeconds;
|
||||
let timerInterval;
|
||||
let unopenedCount;
|
||||
let bombRevealInterval;
|
||||
let squareListeners = {};
|
||||
|
||||
let isFirstClick = true;
|
||||
|
||||
function initializeBoard() {
|
||||
board = [];
|
||||
unopenedCount = 0;
|
||||
|
||||
for (let i = 0; i < size[0]; i++) {
|
||||
const row = [];
|
||||
|
||||
for (let j = 0; j < size[1]; j++) {
|
||||
row.push('.');
|
||||
unopenedCount += 1;
|
||||
}
|
||||
|
||||
board.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
function generateMines(firstClickX, firstClickY) {
|
||||
let i = 0;
|
||||
while (i < mines) {
|
||||
const mineX = randomInt(0, size[0]);
|
||||
const mineY = randomInt(0, size[1]);
|
||||
console.log(mineX);
|
||||
console.log(mineY);
|
||||
|
||||
if (!((mineX >= firstClickX - 1 && mineX <= firstClickX + 1 && mineY >= firstClickY - 1 && mineY <= firstClickY + 1) || board[mineX][mineY] === 'B')) {
|
||||
board[mineX][mineY] = 'B';
|
||||
i++;
|
||||
unopenedCount -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function countMinesAroundSquare(row, column) {
|
||||
let mines = 0;
|
||||
|
||||
for (const direction of directions) {
|
||||
const rowToCheck = row + direction[0];
|
||||
const columnToCheck = column + direction[1];
|
||||
|
||||
if (rowToCheck < 0 || rowToCheck >= size[0] || columnToCheck < 0 || columnToCheck >= size[1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (board[rowToCheck][columnToCheck].includes('B')) {
|
||||
mines += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return mines;
|
||||
}
|
||||
|
||||
function stopGame(hasWon) {
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
}
|
||||
|
||||
for (const square of document.getElementsByClassName('square')) {
|
||||
const listener = squareListeners[square.id];
|
||||
|
||||
square.removeEventListener('mousedown', listener);
|
||||
}
|
||||
|
||||
squareListeners = {};
|
||||
|
||||
const gameResult = document.getElementById('result');
|
||||
|
||||
if (hasWon) {
|
||||
gameResult.innerText = 'You win!';
|
||||
gameResult.className = 'win';
|
||||
} else {
|
||||
gameResult.innerText = 'You lose.';
|
||||
gameResult.className = 'defeat';
|
||||
}
|
||||
|
||||
document.getElementById('result-container').classList.remove('hide');
|
||||
}
|
||||
|
||||
function hasWon() {
|
||||
return unopenedCount === 0;
|
||||
}
|
||||
|
||||
function revealSquare(row, column) {
|
||||
const mineCount = countMinesAroundSquare(row, column);
|
||||
|
||||
if (board[row][column] == 'F') {
|
||||
flagCount += 1;
|
||||
renderFlagsCount();
|
||||
}
|
||||
|
||||
if (board[row][column] == '.') {
|
||||
unopenedCount -= 1;
|
||||
}
|
||||
|
||||
board[row][column] = mineCount.toString();
|
||||
|
||||
const squareElement = document.getElementById(`${row}-${column}`);
|
||||
|
||||
squareElement.innerHTML = squareTexts[board[row][column]];
|
||||
squareElement.className = 'square ' + squareClassNames[board[row][column]];
|
||||
|
||||
if (hasWon()) {
|
||||
stopGame(true);
|
||||
}
|
||||
|
||||
if (mineCount === 0) {
|
||||
for (const direction of directions) {
|
||||
const rowToOpen = row + direction[0];
|
||||
const columnToOpen = column + direction[1];
|
||||
|
||||
if (rowToOpen < 0 || rowToOpen >= size[0] || columnToOpen < 0 || columnToOpen >= size[1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (['.', 'F'].includes(board[rowToOpen][columnToOpen])) {
|
||||
revealSquare(rowToOpen, columnToOpen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function switchFlag(row, column) {
|
||||
const squareValue = board[row][column];
|
||||
if (['.', 'B'].includes(squareValue)) {
|
||||
board[row][column] = squareValue === 'B' ? 'BF' : 'F';
|
||||
|
||||
flagCount -= 1;
|
||||
renderFlagsCount();
|
||||
|
||||
const squareElement = document.getElementById(`${row}-${column}`);
|
||||
|
||||
squareElement.innerHTML = squareTexts[squareValue === 'B' ? 'BF' : 'F'];
|
||||
squareElement.className = 'square ' + squareClassNames[squareValue === 'BF' ? 'B' : '.'];
|
||||
} else if (['BF', 'F'].includes(squareValue)) {
|
||||
board[row][column] = squareValue === 'BF' ? 'B' : '.';
|
||||
|
||||
flagCount += 1;
|
||||
renderFlagsCount();
|
||||
|
||||
const squareElement = document.getElementById(`${row}-${column}`);
|
||||
|
||||
squareElement.innerHTML = squareTexts[squareValue === 'BF' ? 'B' : '.'];
|
||||
squareElement.className = 'square ' + squareClassNames[squareValue === 'BF' ? 'B' : '.'];
|
||||
}
|
||||
}
|
||||
|
||||
function loseGame() {
|
||||
const bombPositions = [];
|
||||
|
||||
for (let i = 0; i < size[0]; i++) {
|
||||
for (let j = 0; j < size[1]; j++) {
|
||||
if (['B', 'BF'].includes(board[i][j])) {
|
||||
bombPositions.push(`${i}-${j}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bombRevealInterval = setInterval(() => {
|
||||
if (bombPositions.length == 0) {
|
||||
clearInterval(bombRevealInterval);
|
||||
} else {
|
||||
const squareElement = document.getElementById(bombPositions.shift());
|
||||
|
||||
squareElement.innerHTML = squareTexts['X'];
|
||||
squareElement.className = 'square ' + squareClassNames['X'];
|
||||
}
|
||||
}, 100);
|
||||
|
||||
stopGame(false);
|
||||
}
|
||||
|
||||
function configureSquareClickEvent(square) {
|
||||
const onClick = event => {
|
||||
const row = parseInt(square.id.split('-')[0]);
|
||||
const column = parseInt(square.id.split('-')[1]);
|
||||
|
||||
if (event.which == 3) {
|
||||
switchFlag(row, column);
|
||||
} else {
|
||||
if (isFirstClick) {
|
||||
isFirstClick = false;
|
||||
|
||||
generateMines(row, column);
|
||||
}
|
||||
|
||||
const squareValue = board[row][column];
|
||||
|
||||
switch (squareValue) {
|
||||
case '.':
|
||||
revealSquare(row, column);
|
||||
|
||||
break;
|
||||
case 'B':
|
||||
loseGame();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
squareListeners[square.id] = onClick;
|
||||
|
||||
square.addEventListener('mousedown', onClick);
|
||||
}
|
||||
|
||||
function renderBoard() {
|
||||
const boardElement = document.getElementById('board');
|
||||
boardElement.innerHTML = '';
|
||||
|
||||
for (const row in board) {
|
||||
const rowElement = document.createElement('div');
|
||||
|
||||
rowElement.classList.add('row');
|
||||
|
||||
for (const column in board[row]) {
|
||||
const squareValue = board[row][column];
|
||||
const squareElement = document.createElement('div');
|
||||
|
||||
squareElement.id = `${row}-${column}`;
|
||||
squareElement.className = `square ${squareClassNames[squareValue]}`;
|
||||
squareElement.innerHTML = squareTexts[squareValue];
|
||||
|
||||
configureSquareClickEvent(squareElement);
|
||||
|
||||
rowElement.appendChild(squareElement);
|
||||
}
|
||||
|
||||
boardElement.appendChild(rowElement);
|
||||
}
|
||||
|
||||
boardElement.classList.remove('hide');
|
||||
document.getElementById('board-header').classList.remove('hide');
|
||||
}
|
||||
|
||||
function renderFlagsCount() {
|
||||
const flagsCountContainerElement = document.getElementById('flags-count-container');
|
||||
const flagsCountElement = document.getElementById('flags-count');
|
||||
|
||||
flagsCountElement.innerText = flagCount;
|
||||
flagsCountContainerElement.classList.remove('hide');
|
||||
}
|
||||
|
||||
function renderResultText() {
|
||||
const gameResult = document.getElementById('result');
|
||||
|
||||
gameResult.innerText = '';
|
||||
|
||||
document.getElementById('result-container').classList.add('hide');
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
timeInSeconds = 0;
|
||||
|
||||
document.getElementById('timer-container').classList.remove('hide');
|
||||
const timer = document.getElementById('timer');
|
||||
|
||||
timer.innerText = '00:00';
|
||||
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
}
|
||||
|
||||
timerInterval = setInterval(function() {
|
||||
timeInSeconds += 1;
|
||||
|
||||
const hours = Math.floor(timeInSeconds / 3600).toString();
|
||||
const minutes = Math.floor((timeInSeconds % 3600) / 60).toString();
|
||||
const seconds = (timeInSeconds % 60).toString();
|
||||
|
||||
let timerText = `${hours > 0 ? hours.padStart(2, '0') + ':' : ''}${minutes.padStart(2, '0')}:${seconds.padStart(2, '0')}`;
|
||||
|
||||
timer.innerText = timerText;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function loadGame() {
|
||||
isFirstClick = true;
|
||||
|
||||
initializeBoard();
|
||||
renderBoard();
|
||||
renderFlagsCount();
|
||||
renderResultText();
|
||||
startTimer();
|
||||
}
|
||||
|
||||
document.getElementById('start-game').addEventListener('click', function() {
|
||||
const difficulty = difficulties[document.getElementById('difficulty').value];
|
||||
|
||||
console.log(difficulty);
|
||||
|
||||
size = difficulty['size'];
|
||||
mines = difficulty['mines'];
|
||||
flagCount = mines;
|
||||
|
||||
if (bombRevealInterval) {
|
||||
clearInterval(bombRevealInterval);
|
||||
}
|
||||
|
||||
loadGame();
|
||||
});
|
||||
|
||||
document.getElementById('board').addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
function randomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,500;0,700;1,400&display=swap');
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 400 16px Roboto, sans-serif;
|
||||
background: radial-gradient(#777, #333);
|
||||
color: #eee;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
input, button, textarea {
|
||||
font: 400 18px Roboto, sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#difficulty-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
#difficulty {
|
||||
margin-left: 8px;
|
||||
padding: 4px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#board-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin: 16px 16px 0 16px;
|
||||
background: black;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#board-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
#board {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin: 4px;
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
#start-game {
|
||||
margin-top: 16px;
|
||||
padding: 4px 16px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: #0F0;
|
||||
color: #555;
|
||||
transition: all 1s ease;
|
||||
}
|
||||
|
||||
#start-game:hover {
|
||||
transform: scale(1.1) perspective(1px)
|
||||
}
|
||||
|
||||
#start-game:active {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.square {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-top: 1px solid black;
|
||||
border-left: 1px solid black;
|
||||
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.row .square:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.row:first-child .square {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.square:hover {
|
||||
background: #CCC;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.unopened {
|
||||
background: #AAA;
|
||||
}
|
||||
|
||||
.opened {
|
||||
background: #EEE;
|
||||
}
|
||||
|
||||
.bomb {
|
||||
background: #E00;
|
||||
}
|
||||
|
||||
.bomb img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.one {
|
||||
color: #00F;
|
||||
}
|
||||
|
||||
.two {
|
||||
color: #070;
|
||||
}
|
||||
|
||||
.three {
|
||||
color: #F00;
|
||||
}
|
||||
|
||||
.four {
|
||||
color: #007;
|
||||
}
|
||||
|
||||
.five {
|
||||
color: #700;
|
||||
}
|
||||
|
||||
.six {
|
||||
color: #077;
|
||||
}
|
||||
|
||||
.seven {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.eight {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.win {
|
||||
color: #0D0;
|
||||
}
|
||||
|
||||
.defeat {
|
||||
color: #D00;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
Loading…
Reference in New Issue