initial commit
This commit is contained in:
commit
fded93a525
21 changed files with 5384 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.*~
|
||||||
|
*.*swp
|
43
ayo.html
Normal file
43
ayo.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Document</title>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="max-width: 270px; margin: 0px auto;">
|
||||||
|
<h3 style="border-bottom: 1px solid rgb(192, 192, 192); padding-bottom: 10px;">Best Times (beginner)</h3>
|
||||||
|
<ol style="list-style: none; text-align: left; margin-left: -40px; margin-top: -15px;">
|
||||||
|
<li>#1: <em>isji</em> 02.0</li>
|
||||||
|
<li>#2: <em>jenn</em> 04.1</li>
|
||||||
|
<li>#3: <em>isji</em> 06.5</li>
|
||||||
|
<li>#4: <em>isji</em> 06.9</li>
|
||||||
|
<li>#5: <em>isji</em> 06.9</li>
|
||||||
|
<li>#6: <em>Andi</em> 07.5</li>
|
||||||
|
<li>#7: <em>Kardz</em> 08.0</li>
|
||||||
|
<li>#8: <em>Kardz</em> 08.2</li>
|
||||||
|
<li>#9: <em>isji</em> 08.2</li>
|
||||||
|
<li>#10: <em>isji</em> 08.4</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="updateLeaders()">Update Leaders</button>
|
||||||
|
|
||||||
|
<!-- The core Firebase JS SDK is always required and must be listed first -->
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-app.js"></script>
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-firestore.js"></script>
|
||||||
|
|
||||||
|
<script type=module src="ayo.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
7
ayo.js
Normal file
7
ayo.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { LeaderBoardService } from '../services/leader-board.service.js';
|
||||||
|
|
||||||
|
const leaderBoard = new LeaderBoardService('mw-leaders', 'mw-all');
|
||||||
|
|
||||||
|
window.updateLeaders = () => {
|
||||||
|
leaderBoard.updateTimeStampsLeaders();
|
||||||
|
}
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
50
index.html
Normal file
50
index.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
|
||||||
|
<!-- Tell the browser not to cache -->
|
||||||
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||||
|
<meta http-equiv="Pragma" content="no-cache" />
|
||||||
|
<meta http-equiv="Expires" content="0" />
|
||||||
|
|
||||||
|
<meta name="Description" content="Play Minesweeper online for FREE!" />
|
||||||
|
<title>Minesweeper</title>
|
||||||
|
|
||||||
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-113797180-1"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'UA-113797180-1');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/main.css" />
|
||||||
|
<link rel="stylesheet" href="/services/loading/loading.css" />
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="body-wrapper">
|
||||||
|
<div id="app">
|
||||||
|
Please use Chrome or Firefox. Sorry for the inconvenience. Please buy me coffee.
|
||||||
|
<br />
|
||||||
|
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type='text/javascript' src='https://ko-fi.com/widgets/widget_2.js'></script><script type='text/javascript'>kofiwidget2.init('Buy me a coffee', '#29abe0', 'ayoayco');kofiwidget2.draw();</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- The core Firebase JS SDK is always required and must be listed first -->
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-app.js"></script>
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-firestore.js"></script>
|
||||||
|
|
||||||
|
<script type="module" src="/dist/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
11
instructions
Normal file
11
instructions
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<div id="instructions" class="hint-wrapper">
|
||||||
|
<h1 class="pointer">Instructions</h1>
|
||||||
|
<ol class="body instructions">
|
||||||
|
<li>Clicking a cell which doesn't have a bomb reveals the number of surrounding bombs. Use this information plus some guess work to avoid opening the bombs.</li>
|
||||||
|
<li>To open a cell, click on it. To flag a cell you think is a bomb, right-click.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div id="pro-tip" class="hint-wrapper">
|
||||||
|
<h1 class="pointer">Pro Tip</h1>
|
||||||
|
<span class="body hint">Clicking an open cell that has the correct number of flagged neighboring bombs will open all remaining unopened neighbor cells all at once. If an incorrect number of neighbors are flagged, or all neighbors are flagged or open, clicking the cell has no effect. If an incorrect neighbor is flagged, this will cause instant death.</span>
|
||||||
|
</div>
|
229
main.css
Normal file
229
main.css
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
/*
|
||||||
|
initial code from: https://codepen.io/101Computing/pen/wEbEqx
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* helpers */
|
||||||
|
.float-left {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.float-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.clear-both {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: black;
|
||||||
|
color: #DDDDDD;
|
||||||
|
font-family: medium-content-sans-serif-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif !important;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: -webkit-linear-gradient(90deg,#ff8a00,#e52e71);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border-spacing: 0px;
|
||||||
|
border: 4px solid #c0c0c0;
|
||||||
|
background: #c0c0c0;
|
||||||
|
font-family: courier new;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid tr td span {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
position: absolute;
|
||||||
|
margin: -8.5px 0 0 -4.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid tr td {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
max-width: 15px;
|
||||||
|
max-height: 15px;
|
||||||
|
min-width: 15px;
|
||||||
|
min-height: 15px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: xx-small;
|
||||||
|
cursor: default !important;
|
||||||
|
padding: 0px;
|
||||||
|
background: #c0d0c0;
|
||||||
|
border: 1px solid #c0c0c0;
|
||||||
|
-moz-user-select: none; -webkit-user-select: none; -ms-user-select:none; user-select:none;-o-user-select:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD[data-status=default],
|
||||||
|
#grid TR TD[data-status=flagged] {
|
||||||
|
border: 1px outset #ececec;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD.mine {
|
||||||
|
background: #dd5050;
|
||||||
|
border: 1px inset orange;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#0000ff, #008100, #ff1300, #000083, #810500, #2a9494, #000000, #808080;
|
||||||
|
*/
|
||||||
|
|
||||||
|
#grid TR TD[data-value="1"] {
|
||||||
|
color: #0000ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD[data-value="2"] {
|
||||||
|
color: #008100;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD[data-value="3"] {
|
||||||
|
color: #ff1300;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD[data-value="4"] {
|
||||||
|
color: #000083;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD[data-value="5"] {
|
||||||
|
color: #810500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD[data-value="6"] {
|
||||||
|
color: #2a9494;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD[data-value="7"] {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD[data-value="8"] {
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#grid TR TD.flag {
|
||||||
|
background: #80d080;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD.wrong span,
|
||||||
|
#grid TR TD.correct span {
|
||||||
|
font-size: xx-small !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD.wrong {
|
||||||
|
background: orange;
|
||||||
|
color: #FF0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid TR TD.correct {
|
||||||
|
background: #00FF00 !important;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.hint-wrapper {
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 0 30px;
|
||||||
|
margin: 0 auto 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.hint,
|
||||||
|
ol.instructions li {
|
||||||
|
font-size: small;
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The Modal (background) */
|
||||||
|
.modal {
|
||||||
|
position: fixed; /* Stay in place */
|
||||||
|
z-index: 1; /* Sit on top */
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%; /* Full width */
|
||||||
|
height: 100%; /* Full height */
|
||||||
|
overflow: auto; /* Enable scroll if needed */
|
||||||
|
background-color: rgb(0,0,0); /* Fallback color */
|
||||||
|
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Content/Box */
|
||||||
|
.modal-content {
|
||||||
|
background: #efffef;
|
||||||
|
margin: 5% auto 0; /* 15% from the top and centered */
|
||||||
|
padding: 20px;
|
||||||
|
border: 5px solid #c0e0d0;
|
||||||
|
width: 60%; /* Could be more or less, depending on screen size */
|
||||||
|
box-shadow: 1px 1px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 7px;
|
||||||
|
line-height: 36px!important;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
box-shadow: 1px 1px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 7px;
|
||||||
|
line-height: 36px!important;
|
||||||
|
min-width: 150px;
|
||||||
|
display: inline-block!important;
|
||||||
|
background-color: #29abe0;
|
||||||
|
padding: 2px 12px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 0 none #fff !important;
|
||||||
|
font-family: 'Quicksand',Helvetica,Century Gothic,sans-serif !important;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: none;
|
||||||
|
font-weight: 700!important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.announcement-action {
|
||||||
|
font-size: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#body-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 30px;
|
||||||
|
right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** mobile **/
|
||||||
|
@media only screen and (max-width: 823px) {
|
||||||
|
#grid tr td {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
max-width: 20px;
|
||||||
|
max-height: 20px;
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
.btn-container {
|
||||||
|
position: inherit;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
3676
package-lock.json
generated
Normal file
3676
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
20
package.json
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "mw",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "http-server .",
|
||||||
|
"watch:dev": "node .\\node_modules\\webpack\\bin\\webpack.js --watch --mode development .",
|
||||||
|
"buildprod": "node .\\node_modules\\webpack\\bin\\webpack.js ."
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"webpack": "^4.41.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"webpack-cli": "^3.3.10"
|
||||||
|
}
|
||||||
|
}
|
17
services/db.service.js
Normal file
17
services/db.service.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
export class DatabaseService {
|
||||||
|
constructor() {
|
||||||
|
// Your web app's Firebase configuration
|
||||||
|
const config = {
|
||||||
|
apiKey: "AIzaSyAbDzNHCSFh59e3r5sZA4_2ZHJnJ6SCCxM",
|
||||||
|
authDomain: "moment-188701.firebaseapp.com",
|
||||||
|
databaseURL: "https://moment-188701.firebaseio.com",
|
||||||
|
projectId: "secure-moment-188701",
|
||||||
|
storageBucket: "secure-moment-188701.appspot.com",
|
||||||
|
messagingSenderId: "113827947104",
|
||||||
|
appId: "1:113827947104:web:b176f746d8358302c51905"
|
||||||
|
};
|
||||||
|
// Initialize Firebase
|
||||||
|
firebase.initializeApp(config);
|
||||||
|
this.store = firebase.firestore();
|
||||||
|
}
|
||||||
|
}
|
41
services/dialog.service.js
Normal file
41
services/dialog.service.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
Author: Ayo Ayco
|
||||||
|
Email: ramon.aycojr@gmail.com
|
||||||
|
Website: AyoAyco.com
|
||||||
|
Blog: FullHacker.com
|
||||||
|
Live: games.fullhacker.com/minesweeper
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const DialogService = function() {
|
||||||
|
let isOpen = false;
|
||||||
|
let isInitialized = false;
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'dialog-wrapper';
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'dialog-container';
|
||||||
|
|
||||||
|
// add dialog wrapper and container elements
|
||||||
|
this.initialize = function() {
|
||||||
|
const bodyElement = document.getElementsByTagName('body')[0];
|
||||||
|
wrapper.appendChild(container);
|
||||||
|
bodyElement.appendChild(wrapper);
|
||||||
|
isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.promptMessage = function(message) {
|
||||||
|
isOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeDialog = function() {
|
||||||
|
if (isOpen) {
|
||||||
|
}
|
||||||
|
isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInitialized = function() {
|
||||||
|
return isInitialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
164
services/leader-board.service.js
Normal file
164
services/leader-board.service.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import { DatabaseService } from "./db.service.js";
|
||||||
|
import { TimerService } from "./timer.service.js";
|
||||||
|
import { UserService } from "./user.service.js";
|
||||||
|
import { LoadingService } from "./loading/loading.js";
|
||||||
|
|
||||||
|
const dbService = new DatabaseService();
|
||||||
|
const timerService = new TimerService();
|
||||||
|
const loadingService = new LoadingService();
|
||||||
|
const db = dbService.store;
|
||||||
|
const user = new UserService();
|
||||||
|
let previousLevel;
|
||||||
|
|
||||||
|
|
||||||
|
export class LeaderBoardService {
|
||||||
|
constructor(leaders, all) {
|
||||||
|
this.leaders = db.collection(leaders);
|
||||||
|
this.all = db.collection(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimeStampsLeaders() {
|
||||||
|
const levels = ['beginner', 'intermediate', 'expert'];
|
||||||
|
|
||||||
|
levels.forEach(level => {
|
||||||
|
const collection = this.leaders.doc(level).collection('games');
|
||||||
|
collection.get()
|
||||||
|
.then(res => {
|
||||||
|
const levelArray = res.docs.map(doc => ({id: doc.id, ...doc.data()}))
|
||||||
|
// console.log(level+": ", levelArray);
|
||||||
|
|
||||||
|
levelArray.forEach(leaderGame => {
|
||||||
|
// const leaderGame = levelArray[0];
|
||||||
|
const leaderTime = leaderGame.time;
|
||||||
|
const browser = leaderGame.browserId;
|
||||||
|
this.all.doc(browser).collection('games')
|
||||||
|
.get().then(games => {
|
||||||
|
const allGames = games.docs.map(doc => ({id: doc.id, games: {...doc.data()}}));
|
||||||
|
console.log(level + '...........' + browser);
|
||||||
|
allGames.forEach(day => {
|
||||||
|
const keys = Object.keys(day.games);
|
||||||
|
const winningKeys = keys.filter(key => day.games[key].status === 'win');
|
||||||
|
winningKeys.forEach(key => {
|
||||||
|
const game = day.games[key];
|
||||||
|
const dateString = [day.id, key].join(' ').replace(/_/g, ' ');
|
||||||
|
const newGame = {time_stamp: new Date(dateString), ...leaderGame};
|
||||||
|
if (game.time === leaderTime) {
|
||||||
|
console.log('updated', newGame);
|
||||||
|
// collection.doc(leaderGame.id).get().then(res => console.log(res.data()));
|
||||||
|
collection.doc(leaderGame.id).set(newGame);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update(level, displayElement, title) {
|
||||||
|
if (level !== previousLevel) {
|
||||||
|
loadingService.addLoading(displayElement);
|
||||||
|
previousLevel = level;
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
this.lastPlace = Number.MAX_SAFE_INTEGER;
|
||||||
|
this.topList = this.leaders.doc(level)
|
||||||
|
.collection('games').orderBy('time').limit(10);
|
||||||
|
this.unsubscribe = this.setListener(this.topList, displayElement, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
renderList(displayElement, title, docs) {
|
||||||
|
if (!displayElement) return;
|
||||||
|
|
||||||
|
displayElement.innerHTML = '';
|
||||||
|
const leaderHeading = document.createElement('h3');
|
||||||
|
leaderHeading.innerText = title;
|
||||||
|
leaderHeading.style.borderBottom = '1px solid #c0c0c0';
|
||||||
|
leaderHeading.style.paddingBottom = '10px';
|
||||||
|
|
||||||
|
|
||||||
|
displayElement.style.maxWidth = '270px';
|
||||||
|
displayElement.style.margin = '0 auto';
|
||||||
|
|
||||||
|
const leaderList = document.createElement('div');
|
||||||
|
|
||||||
|
leaderList.innerHTML = '';
|
||||||
|
leaderList.style.listStyle = 'none';
|
||||||
|
leaderList.style.textAlign = 'left';
|
||||||
|
leaderList.style.marginTop = '-15px';
|
||||||
|
|
||||||
|
if (docs && docs.length) {
|
||||||
|
let i = 1;
|
||||||
|
docs.forEach(game => {
|
||||||
|
if (game) {
|
||||||
|
const prettyTime = timerService.pretty(game.data().time);
|
||||||
|
const name = game.data().name || 'Anonymous';
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.style.display = 'flex';
|
||||||
|
const nameElement =document.createElement('div'); // `<span class="ellipsis" title="${name}">${name}</span>` ;
|
||||||
|
nameElement.innerHTML = name;
|
||||||
|
nameElement.setAttribute('title', name);
|
||||||
|
nameElement.style.textOverflow = 'ellipsis';
|
||||||
|
nameElement.style.whiteSpace = 'nowrap';
|
||||||
|
nameElement.style.overflow = 'hidden';
|
||||||
|
nameElement.style.padding = '0 5px';
|
||||||
|
nameElement.style.cursor = 'pointer';
|
||||||
|
nameElement.style.fontWeight = 'bold';
|
||||||
|
nameElement.style.fontStyle = 'italic';
|
||||||
|
|
||||||
|
const indexElement = document.createElement('div');
|
||||||
|
indexElement.innerText = `#${i++}`;
|
||||||
|
|
||||||
|
const timeElement = document.createElement('div');
|
||||||
|
timeElement.innerText = prettyTime;
|
||||||
|
|
||||||
|
item.append(indexElement, nameElement, timeElement);
|
||||||
|
leaderList.append(item);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (docs.length >= 10) {
|
||||||
|
this.lastPlace = docs[9].data().time;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayElement.append(leaderHeading, leaderList);
|
||||||
|
} else {
|
||||||
|
const message = document.createElement('em');
|
||||||
|
message.innerText = 'Be the first to the top!';
|
||||||
|
displayElement.append(leaderHeading, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setListener(collection, displayElement, title) {
|
||||||
|
return collection.onSnapshot(list => this.renderList(displayElement, title, list.docs));
|
||||||
|
}
|
||||||
|
|
||||||
|
send(game, key) {
|
||||||
|
const sessionId = new Date().toDateString().replace(/\s/g, '_');
|
||||||
|
const gameId = new Date().toTimeString().replace(/\s/g, '_');
|
||||||
|
const data = {};
|
||||||
|
game = {
|
||||||
|
time_stamp: new Date(),
|
||||||
|
...game
|
||||||
|
}
|
||||||
|
data[gameId] = game;
|
||||||
|
this.all.doc(user.browserId).collection('games').doc(sessionId).set(data, {merge: true});
|
||||||
|
|
||||||
|
if (game.status === 'win' && game[key] < this.lastPlace) {
|
||||||
|
let name = window.prompt('Top performance! Enter your name:');
|
||||||
|
if (!name) {
|
||||||
|
name = 'Anonymous';
|
||||||
|
}
|
||||||
|
|
||||||
|
const newGame = {
|
||||||
|
name,
|
||||||
|
browserId: user.browserId,
|
||||||
|
...game
|
||||||
|
}
|
||||||
|
|
||||||
|
this.leaders.doc(game.level).collection('games').add(newGame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
services/loading/loading.css
Normal file
55
services/loading/loading.css
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
.lds-ellipsis {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
.lds-ellipsis div {
|
||||||
|
position: absolute;
|
||||||
|
top: 33px;
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||||
|
}
|
||||||
|
.lds-ellipsis div:nth-child(1) {
|
||||||
|
left: 8px;
|
||||||
|
animation: lds-ellipsis1 0.6s infinite;
|
||||||
|
}
|
||||||
|
.lds-ellipsis div:nth-child(2) {
|
||||||
|
left: 8px;
|
||||||
|
animation: lds-ellipsis2 0.6s infinite;
|
||||||
|
}
|
||||||
|
.lds-ellipsis div:nth-child(3) {
|
||||||
|
left: 32px;
|
||||||
|
animation: lds-ellipsis2 0.6s infinite;
|
||||||
|
}
|
||||||
|
.lds-ellipsis div:nth-child(4) {
|
||||||
|
left: 56px;
|
||||||
|
animation: lds-ellipsis3 0.6s infinite;
|
||||||
|
}
|
||||||
|
@keyframes lds-ellipsis1 {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes lds-ellipsis3 {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes lds-ellipsis2 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(24px, 0);
|
||||||
|
}
|
||||||
|
}
|
8
services/loading/loading.js
Normal file
8
services/loading/loading.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export class LoadingService {
|
||||||
|
addLoading(element) {
|
||||||
|
element.innerHTML = '<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>';
|
||||||
|
}
|
||||||
|
removeLoading(element) {
|
||||||
|
element.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
21
services/logger.service.js
Normal file
21
services/logger.service.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
Author: Ayo Ayco
|
||||||
|
Email: ramon.aycojr@gmail.com
|
||||||
|
Website: AyoAyco.com
|
||||||
|
Blog: FullHacker.com
|
||||||
|
Live: games.fullhacker.com/minesweeper
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class LoggerService {
|
||||||
|
debug(message, data) {
|
||||||
|
if (typeof message === 'string') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`LoggerService.debug expects a string as first parameter but got a ${typeof message}`, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
services/storage.service.js
Normal file
31
services/storage.service.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
Author: Ayo Ayco
|
||||||
|
Email: ramon.aycojr@gmail.com
|
||||||
|
Website: AyoAyco.com
|
||||||
|
Blog: FullHacker.com
|
||||||
|
Live: games.fullhacker.com/minesweeper
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class StorageService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToLocal(key, value) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToSession(key, value) {
|
||||||
|
sessionStorage.setItem(key, JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
getFromLocal(key) {
|
||||||
|
const data = localStorage.getItem(key);
|
||||||
|
if (data !== 'undefined') return JSON.parse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFromSession(key) {
|
||||||
|
const data = sessionStorage.getItem(key);
|
||||||
|
if (data !== 'undefined') return JSON.parse(data);
|
||||||
|
}
|
||||||
|
}
|
72
services/timer.service.js
Normal file
72
services/timer.service.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { LoggerService } from "./logger.service.js";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Author: Ayo Ayco
|
||||||
|
Email: ramon.aycojr@gmail.com
|
||||||
|
Website: AyoAyco.com
|
||||||
|
Blog: FullHacker.com
|
||||||
|
Live: games.fullhacker.com/minesweeper
|
||||||
|
*/
|
||||||
|
|
||||||
|
const INTERVAL = 1;
|
||||||
|
|
||||||
|
export class TimerService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.loggerService = new LoggerService();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(el) {
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
this.display = el;
|
||||||
|
this.startTime = undefined;
|
||||||
|
if (this.id) {
|
||||||
|
this.stop()
|
||||||
|
}
|
||||||
|
this.updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (this.running || !this.display) return;
|
||||||
|
|
||||||
|
// run timer
|
||||||
|
this.running = true;
|
||||||
|
this.startTime = new Date().getTime();
|
||||||
|
this.id = window.setInterval(() => this.updateDisplay(), INTERVAL);
|
||||||
|
this.loggerService.debug(`started timer id: ${this.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.running = false;
|
||||||
|
clearInterval(this.id);
|
||||||
|
this.loggerService.debug(`stopped timer id: ${this.id}`);
|
||||||
|
this.id = undefined;
|
||||||
|
return this.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDisplay() {
|
||||||
|
let currentTime = new Date().getTime() - this.startTime;
|
||||||
|
this.time = Math.floor(currentTime / INTERVAL);
|
||||||
|
this.display.innerHTML = this.pretty(this.time) || '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty(duration) {
|
||||||
|
if (!duration) return undefined;
|
||||||
|
var milliseconds = parseInt((duration % 1000) / 100),
|
||||||
|
seconds = Math.floor((duration / 1000) % 60),
|
||||||
|
minutes = Math.floor((duration / (1000 * 60)) % 60),
|
||||||
|
hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
|
||||||
|
|
||||||
|
hours = (hours < 10) ? `0${hours}` : hours;
|
||||||
|
minutes = (minutes < 10) ? `0${minutes}` : minutes;
|
||||||
|
seconds = (seconds < 10) ? `0${seconds}` : seconds;
|
||||||
|
|
||||||
|
return `${this.clean(hours, ':')}${this.clean(minutes, ':')}${this.clean(seconds, '.')}${this.clean(milliseconds, '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
clean(str, separator) {
|
||||||
|
return (str === '00') ? '' : `${str}${separator}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
21
services/user.service.js
Normal file
21
services/user.service.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
export class UserService {
|
||||||
|
constructor() {
|
||||||
|
if (!this.id) {
|
||||||
|
this.browserId = this.generateId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateId() {
|
||||||
|
|
||||||
|
var nav = window.navigator;
|
||||||
|
var screen = window.screen;
|
||||||
|
var guid = nav.mimeTypes.length;
|
||||||
|
guid += nav.userAgent.replace(/\D+/g, '');
|
||||||
|
guid += nav.plugins.length;
|
||||||
|
guid += screen.height || '';
|
||||||
|
guid += screen.width || '';
|
||||||
|
guid += screen.pixelDepth || '';
|
||||||
|
|
||||||
|
return guid;
|
||||||
|
}
|
||||||
|
}
|
13
src/index.js
Normal file
13
src/index.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
Author: Ayo Ayco
|
||||||
|
Email: ramon.aycojr@gmail.com
|
||||||
|
Website: AyoAyco.com
|
||||||
|
Blog: FullHacker.com
|
||||||
|
Live: games.fullhacker.com/minesweeper
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Minesweeper } from './minesweeper.js';
|
||||||
|
|
||||||
|
/** start the game **/
|
||||||
|
const myMinesweeper = new Minesweeper();
|
||||||
|
myMinesweeper.initialize();
|
27
src/levels.js
Normal file
27
src/levels.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
export const levels = {
|
||||||
|
beginner: {
|
||||||
|
rows: 9,
|
||||||
|
cols: 9,
|
||||||
|
mines: 10,
|
||||||
|
name: 'beginner'
|
||||||
|
},
|
||||||
|
intermediate: {
|
||||||
|
rows: 16,
|
||||||
|
cols: 16,
|
||||||
|
mines: 40,
|
||||||
|
name: 'intermediate'
|
||||||
|
},
|
||||||
|
expert: {
|
||||||
|
rows: 16,
|
||||||
|
cols: 30,
|
||||||
|
mines: 99,
|
||||||
|
name: 'expert'
|
||||||
|
},
|
||||||
|
nightmare: {
|
||||||
|
rows: 20,
|
||||||
|
cols: 30,
|
||||||
|
mines: 150,
|
||||||
|
name: 'nightmare'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
874
src/minesweeper.js
Normal file
874
src/minesweeper.js
Normal file
|
@ -0,0 +1,874 @@
|
||||||
|
/*
|
||||||
|
Author: Ayo Ayco
|
||||||
|
Email: ramon.aycojr@gmail.com
|
||||||
|
Website: AyoAyco.com
|
||||||
|
Blog: FullHacker.com
|
||||||
|
Live: games.fullhacker.com/minesweeper
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { StorageService } from '../services/storage.service.js';
|
||||||
|
import { TimerService } from '../services/timer.service.js';
|
||||||
|
import { LoggerService } from '../services/logger.service.js';
|
||||||
|
import { LeaderBoardService } from '../services/leader-board.service.js';
|
||||||
|
import { levels } from './levels.js';
|
||||||
|
|
||||||
|
const VERSION = "0.3.6";
|
||||||
|
const MOBILE_BUSY_DELAY = 250;
|
||||||
|
const PC_BUSY_DELAY = 500;
|
||||||
|
const TEST_MODE = false;
|
||||||
|
|
||||||
|
export const Minesweeper = function() {
|
||||||
|
const _this = this;
|
||||||
|
const storageService = new StorageService();
|
||||||
|
const timerService = new TimerService();
|
||||||
|
const loggerService = new LoggerService();
|
||||||
|
const leaderBoard = new LeaderBoardService('mw-leaders', 'mw-all');
|
||||||
|
|
||||||
|
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('app');
|
||||||
|
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() {
|
||||||
|
|
||||||
|
appElement.innerHTML = '';
|
||||||
|
const heading = `Minesweeper v${VERSION}`;
|
||||||
|
const headingElement = document.createElement('h1');
|
||||||
|
headingElement.innerText = heading;
|
||||||
|
|
||||||
|
const gameBoard = document.createElement('div');
|
||||||
|
gameBoard.setAttribute('id', 'game-board');
|
||||||
|
|
||||||
|
gameBoard.append(initializeToolbar(), grid, initializeFootbar());
|
||||||
|
|
||||||
|
appElement.append(headingElement, gameBoard);
|
||||||
|
generateGrid()
|
||||||
|
// appElement.append(gameWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
|
||||||
|
initializeLeaderBoard();
|
||||||
|
|
||||||
|
timerService.initialize(timerDisplay);
|
||||||
|
updateFlagsCountDisplay();
|
||||||
|
addMines(setting.mines);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
generateGrid();
|
||||||
|
}
|
||||||
|
resetMouseEventFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onblur = function() {
|
||||||
|
resetMouseEventFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.onmouseleave = function() {
|
||||||
|
removeHighlights();
|
||||||
|
}
|
||||||
|
document.oncontextmenu = () => false;
|
||||||
|
document.onmouseup = function() {
|
||||||
|
resetMouseEventFlags();
|
||||||
|
}
|
||||||
|
document.onmousedown = function(e) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
leaderBoard.send(game, 'time');
|
||||||
|
// send google analytics event
|
||||||
|
if (gtag) {
|
||||||
|
gtag('event', 'mw-event', {
|
||||||
|
'event_category' : 'mw-game',
|
||||||
|
'event_label' : 'end-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' : 'start-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 (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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue