first commit
This commit is contained in:
commit
40d4f315d0
|
@ -0,0 +1,7 @@
|
||||||
|
Copyright 2021 madprops
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,5 @@
|
||||||
|
[Live Demo Here](https://madprops.github.io/minesweeper/)
|
||||||
|
|
||||||
|
![](https://i.imgur.com/tHRnZb4.jpg)
|
||||||
|
|
||||||
|
![](https://i.imgur.com/EEs7g4E.jpg)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,164 @@
|
||||||
|
:root {
|
||||||
|
--size: 720px;
|
||||||
|
--padding1: 5px;
|
||||||
|
--padding2: 10px;
|
||||||
|
--bgcolor1: rgb(69, 73, 103);
|
||||||
|
--bgcolor2: lightblue;
|
||||||
|
--bgcolor3: #a8c8d2;
|
||||||
|
--bgcolor4: #87c2f6;
|
||||||
|
--bgcolor5: rgb(209, 227, 233);
|
||||||
|
--bgcolor6: #456784;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 100%;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--bgcolor1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top {
|
||||||
|
background-color: var(--bgcolor2);
|
||||||
|
width: var(--size);
|
||||||
|
font-size: 18px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top > div {
|
||||||
|
padding: var(--padding1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#levels {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
background-color: var(--bgcolor6);
|
||||||
|
user-select: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#levels > div {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--padding1);
|
||||||
|
padding-left: var(--padding2);
|
||||||
|
padding-right: var(--padding2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.level_selected {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
user-select: none;
|
||||||
|
background-color: var(--bgcolor6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#time {
|
||||||
|
margin-left: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--padding1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid {
|
||||||
|
background-color: var(--bgcolor2);
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
border-bottom-left-radius: 1%;
|
||||||
|
border-bottom-right-radius: 1%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
box-shadow: 0 0 0.29rem rgb(95, 120, 189);
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--bgcolor5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 33.3%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.revealed {
|
||||||
|
background-color: var(--bgcolor3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block .number {
|
||||||
|
display: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.revealed .number {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag .number {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine.revealed {
|
||||||
|
background-color: #cd8787
|
||||||
|
}
|
||||||
|
|
||||||
|
.minehit {
|
||||||
|
background-color: pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag {
|
||||||
|
background-color: var(--bgcolor4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine.flag {
|
||||||
|
background-color: var(--bgcolor4);;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mines {
|
||||||
|
padding: var(--padding1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.minehit {
|
||||||
|
border: 5px #b34de2 solid;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bgchange {
|
||||||
|
0% {
|
||||||
|
background-color: var(--bgcolor1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: var(--bgcolor1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.boom {
|
||||||
|
animation: bgchange 500ms forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.face {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,42 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Minesweeper</title>
|
||||||
|
<link rel="shortcut icon" href="img/face_won.png" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||||
|
<script src="js/libs/dom.js"></script>
|
||||||
|
<script src="js/main/base.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
App.init()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="main">
|
||||||
|
<div id="top">
|
||||||
|
<div id="levels">
|
||||||
|
<div id="level_easy" data-level="easy">Easy</div>
|
||||||
|
<div id="level_normal" data-level="normal" class="level_selected">Normal</div>
|
||||||
|
<div id="level_hard" data-level="hard">Hard</div>
|
||||||
|
<div id="level_expert" data-level="expert">Expert</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="info">
|
||||||
|
<img id="face" src="img/face_waiting.png" class="face">
|
||||||
|
<div id="mines"></div>
|
||||||
|
<div id="time"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="grid"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio id="audio_explosion" src="audio/explosion.mp3"></audio>
|
||||||
|
<audio id="audio_click" src="audio/click.mp3"></audio>
|
||||||
|
<audio id="audio_victory" src="audio/victory.mp3"></audio>
|
||||||
|
<audio id="audio_start" src="audio/start.mp3"></audio>
|
||||||
|
</body>
|
|
@ -0,0 +1,125 @@
|
||||||
|
// DOM v1.0.0
|
||||||
|
const DOM = {}
|
||||||
|
DOM.dataset_obj = {}
|
||||||
|
DOM.dataset_id = 0
|
||||||
|
|
||||||
|
// Select a single element
|
||||||
|
DOM.el = (query, root = document) => {
|
||||||
|
return root.querySelector(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select an array of elements
|
||||||
|
DOM.els = (query, root = document) => {
|
||||||
|
return Array.from(root.querySelectorAll(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select a single element or self
|
||||||
|
DOM.el_or_self = (query, root = document) => {
|
||||||
|
let el = root.querySelector(query)
|
||||||
|
|
||||||
|
if (!el) {
|
||||||
|
if (root.classList.contains(query.replace(`.`, ``))) {
|
||||||
|
el = root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select an array of elements or self
|
||||||
|
DOM.els_or_self = (query, root = document) => {
|
||||||
|
let els = Array.from(root.querySelectorAll(query))
|
||||||
|
|
||||||
|
if (els.length === 0) {
|
||||||
|
if (root.classList.contains(query.replace(`.`, ``))) {
|
||||||
|
els = [root]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return els
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone element
|
||||||
|
DOM.clone = (el) => {
|
||||||
|
return el.cloneNode(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone element children
|
||||||
|
DOM.clone_children = (query) => {
|
||||||
|
let items = []
|
||||||
|
let children = Array.from(DOM.el(query).children)
|
||||||
|
|
||||||
|
for (let c of children) {
|
||||||
|
items.push(DOM.clone(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data set manager
|
||||||
|
DOM.dataset = (el, value, setvalue) => {
|
||||||
|
if (!el) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = el.dataset.dataset_id
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
id = DOM.dataset_id
|
||||||
|
DOM.dataset_id += 1
|
||||||
|
el.dataset.dataset_id = id
|
||||||
|
DOM.dataset_obj[id] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setvalue !== undefined) {
|
||||||
|
DOM.dataset_obj[id][value] = setvalue
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return DOM.dataset_obj[id][value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an html element
|
||||||
|
DOM.create = (type, classes = ``, id = ``) => {
|
||||||
|
let el = document.createElement(type)
|
||||||
|
|
||||||
|
if (classes) {
|
||||||
|
let classlist = classes.split(` `).filter(x => x != ``)
|
||||||
|
|
||||||
|
for (let cls of classlist) {
|
||||||
|
el.classList.add(cls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
el.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an event listener
|
||||||
|
DOM.ev = (element, event, callback, extra) => {
|
||||||
|
element.addEventListener(event, callback, extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add multiple event listeners
|
||||||
|
DOM.evs = (element, events, callback, extra) => {
|
||||||
|
for (let event of events) {
|
||||||
|
element.addEventListener(event, callback, extra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like jQuery's nextAll
|
||||||
|
DOM.next_all = function* (e, selector) {
|
||||||
|
while (e = e.nextElementSibling) {
|
||||||
|
if (e.matches(selector)) {
|
||||||
|
yield e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get item index
|
||||||
|
DOM.index = (el) => {
|
||||||
|
return Array.from(el.parentNode.children).indexOf(el)
|
||||||
|
}
|
|
@ -0,0 +1,586 @@
|
||||||
|
const App = {}
|
||||||
|
App.level = `normal`
|
||||||
|
|
||||||
|
App.init = () => {
|
||||||
|
let style = getComputedStyle(document.body)
|
||||||
|
App.size = parseInt(style.getPropertyValue(`--size`))
|
||||||
|
App.main_el = DOM.el(`#main`)
|
||||||
|
App.grid_el = DOM.el(`#grid`)
|
||||||
|
App.mines_el = DOM.el(`#mines`)
|
||||||
|
App.time_el = DOM.el(`#time`)
|
||||||
|
App.levels_el = DOM.el(`#levels`)
|
||||||
|
App.explosion_fx = DOM.el(`#audio_explosion`)
|
||||||
|
App.click_fx = DOM.el(`#audio_click`)
|
||||||
|
App.victory_fx = DOM.el(`#audio_victory`)
|
||||||
|
App.start_fx = DOM.el(`#audio_start`)
|
||||||
|
App.face_el = DOM.el(`#face`)
|
||||||
|
App.start_events()
|
||||||
|
App.start_info()
|
||||||
|
App.start_levels()
|
||||||
|
App.prepare_game()
|
||||||
|
}
|
||||||
|
|
||||||
|
App.start_events = () => {
|
||||||
|
DOM.ev(App.grid_el, `contextmenu`, (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
DOM.ev(document, `visibilitychange`, () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
App.pause()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
DOM.ev(App.grid_el, `mousedown`, () => {
|
||||||
|
App.change_face(`pressing`)
|
||||||
|
})
|
||||||
|
|
||||||
|
DOM.ev(App.grid_el, `mouseup`, () => {
|
||||||
|
App.change_face(`waiting`)
|
||||||
|
})
|
||||||
|
|
||||||
|
DOM.ev(document, `keyup`, (e) => {
|
||||||
|
if (e.key === `Enter`) {
|
||||||
|
App.ask_restart()
|
||||||
|
}
|
||||||
|
else if (e.key === ` `) {
|
||||||
|
App.toggle_pause()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
App.change_face = (s, force = false) => {
|
||||||
|
if (!force && App.over) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
App.face_el.src = `img/face_${s}.png`
|
||||||
|
}
|
||||||
|
|
||||||
|
App.prepare_game = () => {
|
||||||
|
App.game_started = false
|
||||||
|
App.check_level()
|
||||||
|
App.over = false
|
||||||
|
App.num_revealed = 0
|
||||||
|
App.num_clicks = 0
|
||||||
|
App.main_el.classList.remove(`boom`)
|
||||||
|
App.playing = true
|
||||||
|
App.num_mines = App.initial_mines
|
||||||
|
App.create_grid()
|
||||||
|
clearInterval(App.time_interval)
|
||||||
|
App.time = 0
|
||||||
|
App.update_info()
|
||||||
|
App.change_face(`waiting`)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.create_grid = () => {
|
||||||
|
App.grid_el.innerHTML = ``
|
||||||
|
App.grid = []
|
||||||
|
let size = App.size / App.grid_size
|
||||||
|
let x = 0
|
||||||
|
let y = 0
|
||||||
|
let row = []
|
||||||
|
|
||||||
|
for (let xx = 0; xx < App.grid_size; xx++) {
|
||||||
|
for (let yy = 0; yy < App.grid_size; yy++) {
|
||||||
|
let block = document.createElement(`div`)
|
||||||
|
block.style.width = size + `px`
|
||||||
|
block.style.height = size + `px`
|
||||||
|
block.style.left = x + `px`
|
||||||
|
block.style.top = y + `px`
|
||||||
|
block.classList.add(`block`)
|
||||||
|
|
||||||
|
DOM.ev(block, `click`, () => {
|
||||||
|
App.onclick(xx, yy)
|
||||||
|
})
|
||||||
|
|
||||||
|
DOM.ev(block, `contextmenu`, (e) => {
|
||||||
|
App.flag(xx, yy)
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
App.grid_el.append(block)
|
||||||
|
let item = {}
|
||||||
|
item.x = xx
|
||||||
|
item.y = yy
|
||||||
|
item.block = block
|
||||||
|
item.revealed = false
|
||||||
|
row.push(item)
|
||||||
|
x += size
|
||||||
|
}
|
||||||
|
|
||||||
|
App.grid.push(row)
|
||||||
|
row = []
|
||||||
|
x = 0
|
||||||
|
y += size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.shuffle = (arr) => {
|
||||||
|
if (arr.length === 1) {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
const rand = Math.floor(Math.random() * arr.length)
|
||||||
|
return [arr[rand], ...App.shuffle(arr.filter((_, i) => i != rand))]
|
||||||
|
}
|
||||||
|
|
||||||
|
App.start_game = (x, y) => {
|
||||||
|
if (App.game_started) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let pairs = []
|
||||||
|
|
||||||
|
for (let xx = 0; xx < App.grid_size; xx++) {
|
||||||
|
for (let yy = 0; yy < App.grid_size; yy++) {
|
||||||
|
if (xx === x && yy === y) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xx >= x - 1 && xx <= x + 1) {
|
||||||
|
if (yy >= y - 1 && yy <= y + 1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs.push([xx, yy])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let num = 0
|
||||||
|
|
||||||
|
for (let p of App.shuffle(pairs)) {
|
||||||
|
let item = App.grid[p[0]][p[1]]
|
||||||
|
item.mine = true
|
||||||
|
item.block.classList.add(`mine`)
|
||||||
|
num += 1
|
||||||
|
|
||||||
|
if (num >= App.num_mines) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.game_started = true
|
||||||
|
App.check_mines()
|
||||||
|
App.start_time()
|
||||||
|
App.playsound(App.start_fx)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.check_mines = () => {
|
||||||
|
for (let x = 0; x < App.grid_size; x++) {
|
||||||
|
for (let y = 0; y < App.grid_size; y++) {
|
||||||
|
let number = 0
|
||||||
|
|
||||||
|
if (y > 0) {
|
||||||
|
if (App.grid[x][y - 1].mine) {
|
||||||
|
number += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x > 0) {
|
||||||
|
if (App.grid[x - 1][y - 1].mine) {
|
||||||
|
number += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x < App.grid_size - 1) {
|
||||||
|
if (App.grid[x + 1][y - 1].mine) {
|
||||||
|
number += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x > 0) {
|
||||||
|
if (App.grid[x - 1][y].mine) {
|
||||||
|
number += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x < App.grid_size - 1) {
|
||||||
|
if (App.grid[x + 1][y].mine) {
|
||||||
|
number += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y < App.grid_size - 1) {
|
||||||
|
if (App.grid[x][y + 1].mine) {
|
||||||
|
number += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x > 0) {
|
||||||
|
if (App.grid[x - 1][y + 1].mine) {
|
||||||
|
number += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x < App.grid_size - 1) {
|
||||||
|
if (App.grid[x + 1][y + 1].mine) {
|
||||||
|
number += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = App.grid[x][y]
|
||||||
|
item.number = number
|
||||||
|
let text = document.createElement(`div`)
|
||||||
|
text.classList.add(`number`)
|
||||||
|
|
||||||
|
if (item.mine) {
|
||||||
|
text.textContent = `💣️`
|
||||||
|
}
|
||||||
|
else if (number > 0) {
|
||||||
|
text.textContent = number
|
||||||
|
}
|
||||||
|
|
||||||
|
item.og_number = text.textContent
|
||||||
|
item.block.append(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.random_int = (min, max) => {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1) + min)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.onclick = (x, y) => {
|
||||||
|
if (App.over) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!App.playing) {
|
||||||
|
App.unpause()
|
||||||
|
}
|
||||||
|
|
||||||
|
App.start_game(x, y)
|
||||||
|
let item = App.grid[x][y]
|
||||||
|
|
||||||
|
if (item.revealed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
App.num_clicks += 1
|
||||||
|
|
||||||
|
if (item.mine) {
|
||||||
|
item.block.classList.add(`minehit`)
|
||||||
|
App.gameover(`explosion`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (item.revealed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
App.floodfill(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!App.check_status()) {
|
||||||
|
App.playsound(App.click_fx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.setnumber = (item, s) => {
|
||||||
|
DOM.el(`.number`, item.block).textContent = s
|
||||||
|
}
|
||||||
|
|
||||||
|
App.gameover = (mode) => {
|
||||||
|
App.over = true
|
||||||
|
App.playing = false
|
||||||
|
|
||||||
|
for (let row of App.grid) {
|
||||||
|
for (let item of row) {
|
||||||
|
if (item.mine) {
|
||||||
|
item.block.classList.add(`flag`)
|
||||||
|
App.setnumber(item, item.og_number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === `won`) {
|
||||||
|
App.mines_el.textContent = `You cleared all the mines!`
|
||||||
|
App.playsound(App.victory_fx)
|
||||||
|
App.change_face(`won`, true)
|
||||||
|
}
|
||||||
|
else if (mode === `explosion`) {
|
||||||
|
App.mines_el.textContent = `You stepped on a mine!`
|
||||||
|
App.playsound(App.explosion_fx)
|
||||||
|
App.change_face(`lost`, true)
|
||||||
|
}
|
||||||
|
else if (mode === `timeout`) {
|
||||||
|
App.mines_el.textContent = `You ran out of time!`
|
||||||
|
App.playsound(App.explosion_fx)
|
||||||
|
App.change_face(`lost`, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode !== `won`) {
|
||||||
|
App.main_el.classList.add(`boom`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.floodfill = (x, y) => {
|
||||||
|
let item = App.grid[x][y]
|
||||||
|
|
||||||
|
if (item.number > 0) {
|
||||||
|
App.reveal(x, y)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
App.fill(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.fill = (x, y) => {
|
||||||
|
if (x < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x > App.grid.length - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y > App.grid[x].length - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = App.grid[x][y]
|
||||||
|
|
||||||
|
if (item.revealed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let cont = item.number === 0
|
||||||
|
|
||||||
|
if (!item.revealed) {
|
||||||
|
App.reveal(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cont) {
|
||||||
|
App.fill(x - 1, y)
|
||||||
|
App.fill(x + 1, y)
|
||||||
|
App.fill(x, y - 1)
|
||||||
|
App.fill(x, y + 1)
|
||||||
|
App.fill(x - 1, y + 1)
|
||||||
|
App.fill(x + 1, y - 1)
|
||||||
|
App.fill(x + 1, y + 1)
|
||||||
|
App.fill(x - 1, y - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.reveal = (x, y) => {
|
||||||
|
let item = App.grid[x][y]
|
||||||
|
|
||||||
|
if (item.flag) {
|
||||||
|
App.flag(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
item.block.classList.add(`revealed`)
|
||||||
|
item.revealed = true
|
||||||
|
App.num_revealed += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
App.flag = (x, y) => {
|
||||||
|
if (App.over) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!App.playing) {
|
||||||
|
App.unpause()
|
||||||
|
}
|
||||||
|
|
||||||
|
App.start_game(x, y)
|
||||||
|
let item = App.grid[x][y]
|
||||||
|
|
||||||
|
if (item.revealed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.flag = !item.flag
|
||||||
|
|
||||||
|
if (item.flag) {
|
||||||
|
item.block.classList.add(`flag`)
|
||||||
|
App.setnumber(item, `⚑`)
|
||||||
|
App.num_mines -= 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item.block.classList.remove(`flag`)
|
||||||
|
App.setnumber(item, item.og_number)
|
||||||
|
App.num_mines += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
App.update_mines()
|
||||||
|
}
|
||||||
|
|
||||||
|
App.update_mines = () => {
|
||||||
|
let s
|
||||||
|
|
||||||
|
if (App.num_mines === 1) {
|
||||||
|
s = `mines`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s = `mines`
|
||||||
|
}
|
||||||
|
|
||||||
|
App.mines_el.textContent = `${App.num_mines} / ${App.initial_mines} ${s} (${App.grid_size} x ${App.grid_size})`
|
||||||
|
}
|
||||||
|
|
||||||
|
App.start_info = () => {
|
||||||
|
DOM.ev(App.face_el, `click`, () => {
|
||||||
|
App.ask_restart()
|
||||||
|
})
|
||||||
|
|
||||||
|
DOM.ev(App.time_el, `click`, () => {
|
||||||
|
App.toggle_pause()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
App.update_info = () => {
|
||||||
|
App.update_mines()
|
||||||
|
App.update_time()
|
||||||
|
}
|
||||||
|
|
||||||
|
App.timestring = (n) => {
|
||||||
|
return n.toString().padStart(3, `0`)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.update_time = () => {
|
||||||
|
App.time_el.textContent = `Time: ` + App.timestring(App.time) + ` / ` + App.timestring(App.max_time)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.start_time = () => {
|
||||||
|
clearInterval(App.time_interval)
|
||||||
|
|
||||||
|
App.time_interval = setInterval(() => {
|
||||||
|
if (App.playing) {
|
||||||
|
App.time += 1
|
||||||
|
App.update_time()
|
||||||
|
|
||||||
|
if (App.time >= App.max_time) {
|
||||||
|
App.gameover(`timeout`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.check_status = () => {
|
||||||
|
if (App.num_revealed == (App.grid_size * App.grid_size) - App.initial_mines) {
|
||||||
|
App.gameover(`won`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
App.playsound = (el) => {
|
||||||
|
el.pause()
|
||||||
|
el.currentTime = 0
|
||||||
|
el.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
App.toggle_pause = () => {
|
||||||
|
if (App.playing) {
|
||||||
|
App.pause()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
App.unpause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.pause = () => {
|
||||||
|
if (App.over) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!App.game_started) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!App.playing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
App.playing = false
|
||||||
|
App.time_el.textContent += ` (Paused)`
|
||||||
|
}
|
||||||
|
|
||||||
|
App.unpause = () => {
|
||||||
|
if (App.over) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (App.playing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
App.playing = true
|
||||||
|
App.update_time()
|
||||||
|
}
|
||||||
|
|
||||||
|
App.start_levels = () => {
|
||||||
|
DOM.ev(App.levels_el, `click`, (e) => {
|
||||||
|
let level = e.target.dataset.level
|
||||||
|
|
||||||
|
if (level) {
|
||||||
|
if (level === App.level) {
|
||||||
|
App.ask_restart()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let div of DOM.els(`div`, App.levels_el)) {
|
||||||
|
div.classList.remove(`level_selected`)
|
||||||
|
|
||||||
|
if (div.dataset.level === level) {
|
||||||
|
div.classList.add(`level_selected`)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
div.classList.remove(`level_selected`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.level = level
|
||||||
|
App.ask_restart()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
App.check_level = () => {
|
||||||
|
if (App.level === `easy`) {
|
||||||
|
App.initial_mines = 10
|
||||||
|
App.grid_size = 10
|
||||||
|
App.max_time = 100
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (App.level === `normal`) {
|
||||||
|
App.initial_mines = 30
|
||||||
|
App.grid_size = 15
|
||||||
|
App.max_time = 300
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (App.level === `hard`) {
|
||||||
|
App.initial_mines = 60
|
||||||
|
App.grid_size = 20
|
||||||
|
App.max_time = 600
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (App.level === `expert`) {
|
||||||
|
App.initial_mines = 80
|
||||||
|
App.grid_size = 20
|
||||||
|
App.max_time = 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.ask_restart = () => {
|
||||||
|
if (!App.over) {
|
||||||
|
if (App.num_clicks > 1) {
|
||||||
|
if (confirm(`Restart Game?`)) {
|
||||||
|
App.prepare_game()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.prepare_game()
|
||||||
|
}
|
Loading…
Reference in New Issue