mines/js/main/base.js

586 lines
10 KiB
JavaScript

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()
}