385 lines
9.8 KiB
JavaScript
385 lines
9.8 KiB
JavaScript
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;
|
|
}
|