906 lines
28 KiB
JavaScript
906 lines
28 KiB
JavaScript
import {
|
|
LeaderBoardService,
|
|
LoggerService,
|
|
StorageService,
|
|
TimerService
|
|
} from './modules';
|
|
import { levels } from './levels.js';
|
|
|
|
const TEST_MODE = false; // set to true if you want to test the game with visual hints and separate leaderboard
|
|
const VERSION = "0.3.12";
|
|
const MOBILE_BUSY_DELAY = 250;
|
|
const PC_BUSY_DELAY = 500;
|
|
const CASUAL_MODE = false;
|
|
|
|
export const Minesweeper = function(appId) {
|
|
const _this = this;
|
|
const storageService = new StorageService();
|
|
const timerService = new TimerService();
|
|
const loggerService = new LoggerService();
|
|
const leaderBoard = new LeaderBoardService('mw-leaders', 'mw-all', 'mw-config');
|
|
|
|
let grid = document.createElement('table');
|
|
grid.setAttribute('id', 'grid');
|
|
let flagsDisplay = document.createElement('span');
|
|
let smileyDisplay = document.createElement('span');
|
|
let timerDisplay = document.createElement('span');
|
|
let footbar = document.createElement('div');
|
|
let customWrapper = document.createElement('div');
|
|
customWrapper.setAttribute('id', 'custom-wrapper');
|
|
let appElement = document.getElementById(appId);
|
|
if (!appElement) {
|
|
const body = document.getElementsByTagName('body')[0];
|
|
appElement = document.createElement('div');
|
|
body.append(appElement);
|
|
}
|
|
let leaderWrapper = document.createElement('div');
|
|
|
|
let isMobile = false;
|
|
let isLeft = false;
|
|
let isRight = false;
|
|
let pressed = undefined;
|
|
let bothPressed = undefined;
|
|
let skip = false;
|
|
let skipCondition = false;
|
|
let mouseUpCallBackArray = [
|
|
clickCell,
|
|
middleClickCell,
|
|
];
|
|
let mouseDownCallBackArray = [
|
|
highlightCell, // left-click down
|
|
highlightSurroundingCell, // middle-click down
|
|
rightClickCell // right-click down
|
|
]
|
|
let firstClick = true;
|
|
let isBusy = false;
|
|
let clickedCell;
|
|
let cachedSetting = storageService.getFromLocal('setting');
|
|
let setting = cachedSetting || levels.beginner;
|
|
if (TEST_MODE) {
|
|
setting = {
|
|
rows: 10,
|
|
cols: 10,
|
|
mines: 10,
|
|
name: 'test'
|
|
}
|
|
}
|
|
storageService.saveToLocal('setting', setting);
|
|
let flagsCount = setting.mines;
|
|
let minesArray = [];
|
|
|
|
this.initialize = function() {
|
|
const headingElement = document.createElement('h1');
|
|
const gameBoard = document.createElement('div');
|
|
|
|
headingElement.innerText = `Minesweeper v${VERSION}`;
|
|
gameBoard.setAttribute('id', 'game-board');
|
|
gameBoard.append(initializeToolbar(), grid, initializeFootbar());
|
|
appElement.innerHTML = '';
|
|
appElement.append(headingElement, gameBoard);
|
|
appElement.append(initializeSourceLink());
|
|
|
|
generateGrid();
|
|
}
|
|
|
|
function initializeSourceLink() {
|
|
const sourceLink = document.createElement('a');
|
|
sourceLink.href = 'https://github.com/ayoayco/mnswpr';
|
|
sourceLink.innerText = 'Source code';
|
|
sourceLink.target = '_blank';
|
|
sourceLink.style.color = 'white';
|
|
|
|
return sourceLink;
|
|
}
|
|
|
|
function initializeLeaderBoard() {
|
|
const title = `Best Times (${setting.name})`;
|
|
leaderBoard.update(setting.name, leaderWrapper, title);
|
|
|
|
appElement.append(leaderWrapper);
|
|
}
|
|
|
|
function initializeFootbar() {
|
|
const footBar = document.createElement('div');
|
|
|
|
const resetButton = document.createElement('button');
|
|
resetButton.innerText = 'Reset';
|
|
resetButton.onmousedown = () => generateGrid();
|
|
footBar.append(resetButton);
|
|
|
|
let levelsDropdown = document.createElement('select');
|
|
levelsDropdown.onchange = () => updateSetting(levelsDropdown.value);
|
|
|
|
const levelsKeys = Object.keys(levels);
|
|
levelsKeys.forEach(key => {
|
|
const levelOption = document.createElement('option');
|
|
levelOption.value = levels[key].name;
|
|
levelOption.text = capitalize(levels[key].name);
|
|
if (setting.name === levelOption.value) {
|
|
levelOption.selected = true;
|
|
}
|
|
levelsDropdown.add(levelOption, null);
|
|
});
|
|
|
|
// custom level
|
|
// const customOption = document.createElement('option');
|
|
// customOption.onmousedown = () => {}
|
|
// customOption.value = 'custom';
|
|
// customOption.text = 'Custom';
|
|
// levelsDropdown.add(customOption);
|
|
|
|
if (TEST_MODE) {
|
|
const testLevel = document.createElement('span');
|
|
testLevel.innerText = 'Test Mode';
|
|
footBar.append(testLevel);
|
|
} else {
|
|
footBar.append(levelsDropdown);
|
|
}
|
|
|
|
return footBar;
|
|
}
|
|
|
|
function removeCustomOptions() {
|
|
const customCopy = document.getElementById('custom-wrapper');
|
|
if (customCopy) {
|
|
footbar.removeChild(customWrapper);
|
|
}
|
|
}
|
|
|
|
function insertCustomOptions() {
|
|
|
|
const inputElements = [];
|
|
|
|
const rowsInput = document.createElement('input');
|
|
rowsInput.placeholder = 'Rows';
|
|
inputElements.push(rowsInput);
|
|
|
|
const colsInput = document.createElement('input');
|
|
colsInput.placeholder = 'Columns';
|
|
inputElements.push(colsInput);
|
|
|
|
const bombsInput = document.createElement('input');
|
|
bombsInput.placeholder = 'Bombs';
|
|
inputElements.push(bombsInput);
|
|
|
|
const okButton = document.createElement('button');
|
|
okButton.innerText = 'Okay';
|
|
const setting = {rows: rowsInput.value, cols: colsInput.value, bombs: bombsInput.value};
|
|
okButton.onmousedown = () => updateSetting('custom-action', setting);
|
|
|
|
inputElements.forEach(input => {
|
|
input.style.marginRight = '15px';
|
|
input.style.width = '100px';
|
|
input.maxLength = 3;
|
|
input.type = 'number';
|
|
input.width = 50;
|
|
});
|
|
|
|
customWrapper.append(...inputElements, okButton);
|
|
footbar.append(customWrapper);
|
|
}
|
|
|
|
function capitalize(str) {
|
|
if (!str) return '';
|
|
return `${str[0].toUpperCase()}${str.slice(1, str.length)}`;
|
|
}
|
|
|
|
function initializeToolbar() {
|
|
const toolbar = document.createElement('div');
|
|
const toolbarItems = [];
|
|
|
|
const flagsWrapper = document.createElement('div');
|
|
flagsWrapper.append(flagsDisplay)
|
|
flagsWrapper.style.height = '20px';
|
|
toolbar.append(flagsWrapper);
|
|
toolbarItems.push(flagsWrapper);
|
|
|
|
const smileyWrapper = document.createElement('div');
|
|
smileyWrapper.append(smileyDisplay);
|
|
// toolbar.append(smileyWrapper);
|
|
// toolbarItems.push(smileyWrapper);
|
|
|
|
const timerWrapper = document.createElement('div');
|
|
timerWrapper.append(timerDisplay);
|
|
timerWrapper.style.height = '20px';
|
|
toolbar.append(timerWrapper);
|
|
toolbarItems.push(timerWrapper);
|
|
|
|
toolbar.style.cursor = 'pointer';
|
|
toolbar.style.padding = '10px 35px';
|
|
toolbar.style.display = 'flex';
|
|
toolbar.style.justifyContent = 'space-between';
|
|
toolbar.onmousedown = () => generateGrid();
|
|
|
|
return toolbar;
|
|
}
|
|
|
|
function updateSetting(key, custom) {
|
|
if (key === 'custom') {
|
|
insertCustomOptions();
|
|
} else if (key === 'custom-action') {
|
|
console.log('custom', custom);
|
|
} else {
|
|
setting = levels[key];
|
|
storageService.saveToLocal('setting', setting);
|
|
removeCustomOptions();
|
|
generateGrid();
|
|
}
|
|
}
|
|
|
|
|
|
function generateGrid() {
|
|
|
|
//generate 10 by 10 grid
|
|
firstClick = true;
|
|
grid.innerHTML = '';
|
|
grid.oncontextmenu = () => false;
|
|
flagsCount = setting.mines;
|
|
minesArray = [];
|
|
|
|
for (let i = 0; i < setting.rows; i++) {
|
|
let row = grid.insertRow(i);
|
|
row.oncontextmenu = () => false;
|
|
for (let j=0; j<setting.cols; j++) {
|
|
let cell = row.insertCell(j);
|
|
initializeEventHandlers(cell);
|
|
|
|
if ('ontouchstart' in document.documentElement) {
|
|
isMobile = true;
|
|
initializeTouchEventHandlers(cell);
|
|
}
|
|
|
|
let status = document.createAttribute("data-status");
|
|
status.value = "default";
|
|
cell.setAttributeNode(status);
|
|
}
|
|
}
|
|
|
|
let gameStatus = document.createAttribute('game-status');
|
|
gameStatus.value = 'inactive';
|
|
grid.setAttributeNode(gameStatus);
|
|
|
|
appElement.style.minWidth = '260px';
|
|
appElement.style.width = `${grid.offsetWidth + 40}px`;
|
|
appElement.style.margin = '0 auto';
|
|
|
|
if (!CASUAL_MODE) {
|
|
initializeLeaderBoard();
|
|
}
|
|
|
|
timerService.initialize(timerDisplay);
|
|
updateFlagsCountDisplay();
|
|
addMines(setting.mines);
|
|
|
|
// send google analytics event
|
|
if (gtag) {
|
|
gtag('event', 'mw-event', {
|
|
'event_category' : 'mw-game',
|
|
'event_label' : 'loaded-game'
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
function setBusy() {
|
|
isBusy = true;
|
|
if (isMobile) {
|
|
setTimeout(() => isBusy = false, MOBILE_BUSY_DELAY);
|
|
} else {
|
|
setTimeout(() => isBusy = false, PC_BUSY_DELAY);
|
|
}
|
|
}
|
|
|
|
function updateFlagsCountDisplay(count = flagsCount) {
|
|
if (grid.getAttribute('game-status') != 'win') {
|
|
flagsDisplay.innerHTML = `${count}`;
|
|
return;
|
|
}
|
|
flagsDisplay.innerHTML = '😁';
|
|
}
|
|
|
|
function initializeTouchEventHandlers(_cell) {
|
|
let cell = document.createElement('td');
|
|
cell = _cell;
|
|
|
|
let ontouchleave = function(e) {
|
|
if (clickedCell === this) {
|
|
clickedCell = undefined
|
|
}
|
|
}
|
|
cell.addEventListener('touchleave', ontouchleave);
|
|
|
|
let ontouchend = function(e) {
|
|
endTouchTimer();
|
|
}
|
|
cell.addEventListener('touchend', ontouchend);
|
|
|
|
let ontouchstart = function(e) {
|
|
isMobile = true;
|
|
if (!isBusy && typeof e === 'object') {
|
|
startTouchTimer(this);
|
|
}
|
|
}
|
|
cell.addEventListener('touchstart', ontouchstart);
|
|
|
|
}
|
|
|
|
function initializeEventHandlers(_cell) {
|
|
|
|
let cell = document.createElement('td');
|
|
cell = _cell;
|
|
skip = false;
|
|
skipCondition = false;
|
|
|
|
resetMouseEventFlags();
|
|
|
|
document.onkeydown = function(e) {
|
|
if (e.keyCode == 32 || e.keyCode == 113) {
|
|
generateGrid();
|
|
if ('preventDefault' in e) {
|
|
e.preventDefault();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
resetMouseEventFlags();
|
|
}
|
|
|
|
window.onblur = function() {
|
|
resetMouseEventFlags();
|
|
}
|
|
|
|
grid.onmouseleave = function() {
|
|
removeHighlights();
|
|
}
|
|
document.oncontextmenu = () => false;
|
|
document.onmouseup = function() {
|
|
resetMouseEventFlags();
|
|
}
|
|
document.onmousedown = function(e) {
|
|
isMobile = false;
|
|
switch (e.button) {
|
|
case 0: pressed = 'left'; isLeft = true; break;
|
|
case 1: pressed = 'middle'; break;
|
|
case 2: isRight = true; break;
|
|
}
|
|
}
|
|
|
|
// Set grid status to active on first click
|
|
cell.onmouseup = function(e) {
|
|
pressed = undefined;
|
|
let dont = false;
|
|
|
|
if (bothPressed) {
|
|
bothPressed = false;
|
|
if (e.button == '2') {
|
|
skipCondition = true;
|
|
} else if (e.button == '0') {
|
|
dont = true;
|
|
}
|
|
if (getStatus(this) == 'clicked') {
|
|
middleClickCell(this);
|
|
return;
|
|
}
|
|
}
|
|
switch(e.button) {
|
|
case 0: {
|
|
isLeft = false;
|
|
if (skipCondition) {
|
|
skip = true;
|
|
}
|
|
break;
|
|
}
|
|
case 2: isRight = false; break
|
|
}
|
|
removeHighlights();
|
|
if (skip || dont) {
|
|
skip = false;
|
|
skipCondition = false;
|
|
return;
|
|
}
|
|
if (!isBusy && typeof e === 'object' && e.button != '2') {
|
|
mouseUpCallBackArray[e.button].call(_this, this);
|
|
}
|
|
}
|
|
|
|
|
|
cell.onmousedown = function(e) {
|
|
skip = false;
|
|
if (!isBusy && typeof e === 'object') {
|
|
switch(e.button) {
|
|
case 0: isLeft = true; break;
|
|
case 2: isRight = true; break
|
|
}
|
|
|
|
if (isLeft && isRight) {
|
|
bothPressed = true;
|
|
highlightSurroundingCell(this);
|
|
return;
|
|
}
|
|
|
|
if (e.button == '1') {
|
|
pressed = 'middle';
|
|
highlightSurroundingCell(this);
|
|
} else if (e.button == '0') {
|
|
pressed = 'left';
|
|
if (getStatus(this) == 'clicked') {
|
|
highlightSurroundingCell(this);
|
|
} else {
|
|
highlightCell(this);
|
|
}
|
|
}
|
|
|
|
if (e.button == '2') mouseDownCallBackArray[e.button].call(_this, this);
|
|
}
|
|
}
|
|
|
|
cell.onmousemove = function(e) {
|
|
if ((pressed || bothPressed) && typeof e === 'object') {
|
|
removeHighlights();
|
|
/*
|
|
if (!isEqual(clickedCell, cell)) {
|
|
clickedCell = undefined;
|
|
}
|
|
*/
|
|
if (pressed == 'middle' || (isLeft && isRight)) {
|
|
highlightSurroundingCell(this);
|
|
} else if (pressed == 'left') {
|
|
if (getStatus(this) == 'clicked') {
|
|
highlightSurroundingCell(this);
|
|
} else {
|
|
highlightCell(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cell.oncontextmenu = () => false;
|
|
cell.onselectstart = () => false;
|
|
cell.setAttribute('unselectable', 'on');
|
|
}
|
|
|
|
function isEqual(x, y) {
|
|
if (!x) return false;
|
|
return x === y;
|
|
}
|
|
|
|
function startTouchTimer(cell) {
|
|
if (isEqual(clickedCell, cell)) {
|
|
return;
|
|
}
|
|
clickedCell = cell;
|
|
setTimeout(() => {
|
|
if (isEqual(clickedCell, cell)) {
|
|
rightClickCell(cell);
|
|
setBusy();
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
function endTouchTimer() {
|
|
clickedCell = undefined;
|
|
}
|
|
|
|
function resetMouseEventFlags() {
|
|
pressed = undefined;
|
|
bothPressed = undefined;
|
|
isLeft = false;
|
|
isRight = false;
|
|
removeHighlights();
|
|
skip = true;
|
|
}
|
|
|
|
function addMines(minesCount) {
|
|
//Add mines randomly
|
|
for (let i=0; i<minesCount; i++) {
|
|
let row = Math.floor(Math.random() * setting.rows);
|
|
let col = Math.floor(Math.random() * setting.cols);
|
|
let cell = grid.rows[row].cells[col];
|
|
if (isMine(cell)) {
|
|
transferMine();
|
|
} else {
|
|
minesArray.push([row, col]);
|
|
}
|
|
if (TEST_MODE){
|
|
cell.innerHTML = 'X';
|
|
}
|
|
}
|
|
if (TEST_MODE) {
|
|
printMines();
|
|
}
|
|
}
|
|
|
|
function revealMines() {
|
|
if (grid.getAttribute('game-status') == 'done') return;
|
|
//Highlight all mines in red
|
|
const win = grid.getAttribute('game-status') == 'win';
|
|
for (let i=0; i<setting.rows; i++) {
|
|
for(let j=0; j<setting.cols; j++) {
|
|
let cell = grid.rows[i].cells[j];
|
|
if (win) {
|
|
handleWinRevelation(cell);
|
|
} else {
|
|
handleLostRevelation(cell);
|
|
}
|
|
}
|
|
}
|
|
grid.setAttribute('game-status', 'done');
|
|
|
|
const time = timerService.stop();
|
|
const game = {
|
|
time,
|
|
status: win ? 'win' : 'loss',
|
|
level: setting.name,
|
|
time_stamp: new Date(),
|
|
isMobile
|
|
}
|
|
|
|
if (!TEST_MODE) {
|
|
leaderBoard.send(game, 'time');
|
|
}
|
|
|
|
// send google analytics event
|
|
if (gtag) {
|
|
gtag('event', 'mw-event', {
|
|
'event_category' : 'mw-game',
|
|
'event_label' : 'ended-game'
|
|
});
|
|
}
|
|
}
|
|
|
|
function handleWinRevelation(cell) {
|
|
updateFlagsCountDisplay(0);
|
|
if (isMine(cell)) {
|
|
cell.innerHTML = ':)'
|
|
cell.className = 'correct';
|
|
setStatus(cell, 'clicked');
|
|
let correct = document.createAttribute('title');
|
|
correct.value = 'Correct';
|
|
cell.setAttributeNode(correct)
|
|
setStatus(cell, 'clicked');
|
|
}
|
|
}
|
|
|
|
function handleLostRevelation(cell) {
|
|
if (isFlagged(cell)) {
|
|
cell.className = 'flag'
|
|
if (!isMine(cell)) {
|
|
cell.innerHTML = 'X';
|
|
cell.className = 'wrong';
|
|
let wrong = document.createAttribute('title');
|
|
wrong.value = 'Wrong';
|
|
cell.setAttributeNode(wrong);
|
|
} else {
|
|
cell.innerHTML = ':)'
|
|
cell.className = 'correct';
|
|
let correct = document.createAttribute('title');
|
|
correct.value = 'Correct';
|
|
cell.setAttributeNode(correct);
|
|
}
|
|
} else {
|
|
if (isMine(cell)) {
|
|
cell.className = 'mine';
|
|
setStatus(cell, 'clicked');
|
|
}
|
|
}
|
|
}
|
|
|
|
function isOpen(cell) {
|
|
return cell.innerHTML !== '' && !isFlagged(cell);
|
|
}
|
|
|
|
function isFlagged(cell) {
|
|
return getStatus(cell) == 'flagged';
|
|
}
|
|
|
|
function isMine(cell) {
|
|
return getIndex(minesArray, cell) > -1;
|
|
}
|
|
|
|
function removeItem(arr, cell) {
|
|
const index = getIndex(arr, cell);
|
|
if (index > -1) {
|
|
arr.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
function getIndex(arr, cell) {
|
|
const row = cell.parentNode.rowIndex;
|
|
const col = cell.cellIndex;
|
|
let index = -1;
|
|
for (let i = 0; i < arr.length; i++) {
|
|
let rowCol = arr[i]
|
|
if (rowCol[0] === row && rowCol[1] === col) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
function checkLevelCompletion() {
|
|
let levelComplete = true;
|
|
for (let i=0; i<setting.rows; i++) {
|
|
for(let j=0; j<setting.cols; j++) {
|
|
const cell = grid.rows[i].cells[j];
|
|
if (!isMine(cell) && cell.innerHTML=="") levelComplete=false;
|
|
}
|
|
}
|
|
if (levelComplete && grid.getAttribute('game-status') == 'active') {
|
|
grid.setAttribute('game-status', 'win');
|
|
revealMines();
|
|
}
|
|
}
|
|
|
|
function setStatus(cell, status) {
|
|
cell.setAttribute('data-status', status);
|
|
}
|
|
|
|
function getCol(cell) {
|
|
return cell.cellIndex;
|
|
}
|
|
|
|
function getRow(cell) {
|
|
return cell.parentNode.rowIndex;
|
|
}
|
|
|
|
function getStatus(cell) {
|
|
if (!cell) return undefined;
|
|
return cell.getAttribute('data-status');
|
|
}
|
|
|
|
function middleClickCell(cell) {
|
|
if (grid.getAttribute('game-status') != 'active' || getStatus(cell) !== 'clicked') {
|
|
return;
|
|
}
|
|
// check for number of surrounding flags
|
|
const valueString = cell.getAttribute('data-value');
|
|
let cellValue = parseInt(valueString, 10);
|
|
let flagCount = countFlagsAround(cell);
|
|
|
|
if (flagCount === cellValue) {
|
|
clickSurrounding(cell);
|
|
if (TEST_MODE) loggerService.debug('middle click', cell);
|
|
}
|
|
}
|
|
|
|
function countFlagsAround(cell) {
|
|
let flagCount = 0;
|
|
let cellRow = cell.parentNode.rowIndex;
|
|
let cellCol = cell.cellIndex;
|
|
for (let i = Math.max(cellRow-1,0); i <= Math.min(cellRow+1, setting.rows - 1); i++) {
|
|
for(let j = Math.max(cellCol-1,0); j <= Math.min(cellCol+1, setting.cols - 1); j++) {
|
|
if (isFlagged(grid.rows[i].cells[j])) flagCount++;
|
|
}
|
|
}
|
|
return flagCount;
|
|
}
|
|
|
|
function clickSurrounding(cell) {
|
|
if (grid.getAttribute('game-status') != 'active') return;
|
|
let cellRow = cell.parentNode.rowIndex;
|
|
let cellCol = cell.cellIndex;
|
|
for (let i = Math.max(cellRow-1,0); i <= Math.min(cellRow+1, setting.rows - 1); i++) {
|
|
for(let j = Math.max(cellCol-1,0); j <= Math.min(cellCol+1, setting.cols - 1); j++) {
|
|
let currentCell = grid.rows[i].cells[j];
|
|
if (getStatus(currentCell) == 'flagged') continue;
|
|
openCell(currentCell);
|
|
}
|
|
}
|
|
}
|
|
|
|
function increaseFlagsCount() {
|
|
flagsCount++;
|
|
updateFlagsCountDisplay();
|
|
}
|
|
|
|
function decreaseFlagsCount() {
|
|
flagsCount--;
|
|
updateFlagsCountDisplay();
|
|
}
|
|
|
|
function activateGame() {
|
|
grid.setAttribute('game-status', 'active');
|
|
// start timer
|
|
timerService.start();
|
|
// send google analytics event
|
|
if (gtag) {
|
|
gtag('event', 'mw-event', {
|
|
'event_category' : 'mw-game',
|
|
'event_label' : 'activated-game'
|
|
});
|
|
}
|
|
}
|
|
|
|
function gameIsDone() {
|
|
return grid.getAttribute('game-status') == 'over' || grid.getAttribute('game-status') == 'done';
|
|
}
|
|
|
|
function removeHighlights() {
|
|
for (let i=0; i<setting.rows; i++) {
|
|
const rows = grid.rows[i];
|
|
if (!rows) continue;
|
|
for(let j=0; j<setting.cols; j++) {
|
|
let currentCell = grid.rows[i].cells[j];
|
|
if (getStatus(currentCell) == 'highlighted') setStatus(currentCell, 'default');
|
|
}
|
|
}
|
|
}
|
|
|
|
function highlightCell(cell) {
|
|
if (isFlagged(cell)) return;
|
|
if (!gameIsDone() && getStatus(cell) == 'default') setStatus(cell, 'highlighted'); // currentCell.classList.add('highlight');
|
|
}
|
|
|
|
function highlightSurroundingCell(cell) {
|
|
let cellRow = cell.parentNode.rowIndex;
|
|
let cellCol = cell.cellIndex;
|
|
|
|
highlightCell(cell);
|
|
for (let i = Math.max(cellRow-1,0); i <= Math.min(cellRow+1, setting.rows - 1); i++) {
|
|
for(let j = Math.max(cellCol-1,0); j <= Math.min(cellCol+1, setting.cols - 1); j++) {
|
|
let currentCell = grid.rows[i].cells[j];
|
|
highlightCell(currentCell);
|
|
}
|
|
}
|
|
}
|
|
|
|
function rightClickCell(cell) {
|
|
if (isFlagged(cell)) setBusy();
|
|
if (grid.getAttribute('game-status') == 'inactive') {
|
|
activateGame();
|
|
}
|
|
if (grid.getAttribute('game-status') != 'active') return;
|
|
if (getStatus(cell) != 'clicked' && getStatus(cell) != 'empty') {
|
|
if (getStatus(cell) == 'default' || getStatus(cell) == 'highlighted') {
|
|
if (flagsCount <= 0) return;
|
|
cell.className = 'flag';
|
|
decreaseFlagsCount();
|
|
setStatus(cell, 'flagged');
|
|
} else {
|
|
cell.className = '';
|
|
increaseFlagsCount();
|
|
setStatus(cell, 'default');
|
|
}
|
|
if ('vibrate' in navigator) {
|
|
navigator.vibrate(100);
|
|
}
|
|
if (TEST_MODE) loggerService.debug('right click', cell);
|
|
}
|
|
}
|
|
|
|
function clickCell(cell) {
|
|
if (isFlagged(cell)) setBusy();
|
|
if (grid.getAttribute('game-status') == 'inactive') {
|
|
activateGame();
|
|
}
|
|
if (grid.getAttribute('game-status') != 'active') return;
|
|
//Check if the end-user clicked on a mine
|
|
if (TEST_MODE) loggerService.debug('click', cell);
|
|
if (getStatus(cell) == 'flagged' || grid.getAttribute('game-status') == 'over') {
|
|
return;
|
|
} else if (getStatus(cell) == 'clicked') {
|
|
middleClickCell(cell);
|
|
return
|
|
} else if (isMine(cell) && firstClick) {
|
|
// cell.setAttribute('data-mine', 'false');
|
|
removeItem(minesArray, cell);
|
|
transferMine(cell);
|
|
if (TEST_MODE) printMines();
|
|
}
|
|
|
|
openCell(cell);
|
|
}
|
|
|
|
function printMines() {
|
|
let count = 0;
|
|
for (let i = 0; i < setting.rows; i++) {
|
|
for (let j = 0; j < setting.cols; j++) {
|
|
if (isMine(grid.rows[i].cells[j])) {
|
|
loggerService.debug(count++ + ' - mine: [' + i + ',' + j + ']');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function transferMine(cell = undefined) {
|
|
let found = false;
|
|
do {
|
|
let row = Math.floor(Math.random() * setting.rows);
|
|
let col = Math.floor(Math.random() * setting.cols);
|
|
const transferMineToCell = grid.rows[row].cells[col];
|
|
if (isMine(transferMineToCell) || isNeighbor(cell, transferMineToCell)) {
|
|
continue;
|
|
} else {
|
|
minesArray.push([row, col]);
|
|
if (TEST_MODE){
|
|
transferMineToCell.innerHTML = 'X';
|
|
if (TEST_MODE) loggerService.debug('transferred mine to: ' + row + ', ' + col);
|
|
}
|
|
found = true;
|
|
return;
|
|
}
|
|
} while(!found)
|
|
}
|
|
|
|
function isNeighbor(cell, nextCell) {
|
|
if (cell === undefined) {
|
|
return;
|
|
}
|
|
const rowDifference = Math.abs(getRow(cell) - getRow(nextCell));
|
|
const colDifference = Math.abs(getCol(cell) - getCol(nextCell));
|
|
|
|
return (rowDifference === 1) && (colDifference === 1);
|
|
}
|
|
|
|
function countMinesAround(cell) {
|
|
let mineCount=0;
|
|
let cellRow = cell.parentNode.rowIndex;
|
|
let cellCol = cell.cellIndex;
|
|
for (let i = Math.max(cellRow-1,0); i <= Math.min(cellRow+1,setting.rows-1); i++) {
|
|
const rows = grid.rows[i];
|
|
if (!rows) continue;
|
|
for(let j = Math.max(cellCol-1,0); j <= Math.min(cellCol+1,setting.cols-1); j++) {
|
|
const cell = rows.cells[j];
|
|
const mine = isMine(cell);
|
|
if (cell && mine) {
|
|
mineCount++;
|
|
}
|
|
}
|
|
}
|
|
return mineCount;
|
|
}
|
|
|
|
function updateCellValue(cell, value) {
|
|
const spanElement = document.createElement('span');
|
|
spanElement.innerHTML = value;
|
|
cell.innerHTML = '';
|
|
cell.appendChild(spanElement);
|
|
}
|
|
|
|
function handleEmpty(cell) {
|
|
updateCellValue(cell, ' ');
|
|
let cellRow = cell.parentNode.rowIndex;
|
|
let cellCol = cell.cellIndex;
|
|
setStatus(cell, 'empty');
|
|
//Reveal all adjacent cells as they do not have a mine
|
|
for (let y = Math.max(cellRow-1,0); y <= Math.min(cellRow+1, setting.rows - 1); y++) {
|
|
const rows = grid.rows[y];
|
|
if (!rows) continue;
|
|
for(let x = Math.max(cellCol-1,0); x <= Math.min(cellCol+1, setting.cols - 1); x++) {
|
|
//Recursive Call
|
|
const cell = rows.cells[x];
|
|
if (cell && !isOpen(cell)) {
|
|
clickCell(cell);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function openCell(cell) {
|
|
if (grid.getAttribute('game-status') != 'active') return;
|
|
|
|
cell.className="clicked";
|
|
setStatus(cell, 'clicked');
|
|
firstClick = false;
|
|
|
|
if (isMine(cell)) {
|
|
revealMines();
|
|
flagsDisplay.innerHTML = '😱';
|
|
grid.setAttribute('game-status', 'over');
|
|
} else {
|
|
const mineCount = countMinesAround(cell);
|
|
if (mineCount==0) {
|
|
handleEmpty(cell);
|
|
} else {
|
|
updateCellValue(cell, mineCount.toString());
|
|
const dataValue = document.createAttribute('data-value');
|
|
dataValue.value = mineCount;
|
|
cell.setAttributeNode(dataValue);
|
|
}
|
|
//Count and display the number of adjacent mines
|
|
checkLevelCompletion();
|
|
}
|
|
}
|
|
}
|