Cuckoosweeper / game.js
Zok213's picture
Upload 7 files
90d53f1 verified
let isMacOS = false;
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
let iDevice = false;
if (isBrowser && navigator.platform) {
iDevice = navigator.platform.match(/^iP/);
}
if (isBrowser) {
const os = navigator.platform.toLowerCase();
isMacOS = os.includes('mac');
console.log(isMacOS ? "Running on macOS or iOS." : "Running in a browser environment.");
}
const numbers = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣'];
let feedback;
if (isBrowser) {
feedback = document.querySelector('.feedback');
}
class Game {
constructor(cols, rows, number_of_bombs, set) {
this.cols = Number(cols);
this.rows = Number(rows);
this.number_of_bombs = Number(number_of_bombs);
this.number_of_cells = this.cols * this.rows;
this.map = isBrowser ? document.getElementById('map') : null;
if (this.number_of_cells > 2500 || this.number_of_bombs >= this.number_of_cells) {
alert('Invalid game size or bomb count');
return;
}
this.emojiset = set;
this.numbermoji = [this.emojiset[0]].concat(numbers);
this.usetwemoji = false;
this.init();
}
init() {
if (!isBrowser) return;
this.prepareEmoji();
this.map.innerHTML = '';
const grid_data = this.bomb_array();
let row = document.createElement('div');
row.classList.add('row');
grid_data.forEach((isBomb, i) => {
const cell = this.createCell(i, isBomb, grid_data);
row.appendChild(cell);
if ((i + 1) % this.cols === 0) {
this.map.appendChild(row);
row = document.createElement('div');
row.classList.add('row');
}
});
this.resetMetadata();
this.bindEvents();
this.updateBombsLeft();
}
createCell(i, isBomb, grid_data) {
const cellWrapper = document.createElement('span');
cellWrapper.classList.add('cell-wrapper');
const x = (i % this.cols) + 1;
const y = Math.ceil((i + 1) / this.cols);
const neighbors_coords = this.getNeighbors(x, y);
const mine = this.mine(isBomb, neighbors_coords, grid_data, x, y);
cellWrapper.appendChild(mine);
return cellWrapper;
}
getNeighbors(x, y) {
return [
[x, y - 1], [x, y + 1],
[x - 1, y - 1], [x - 1, y], [x - 1, y + 1],
[x + 1, y - 1], [x + 1, y], [x + 1, y + 1]
];
}
mine(isBomb, neighbors_coords, grid_data, x, y) {
const mine = document.createElement('button');
mine.type = 'button';
mine.className = `cell x${x} y${y}`;
mine.isMasked = true;
mine.isBomb = isBomb;
mine.isFlagged = false;
if (!isBomb) {
const neighbors = neighbors_coords.map(([nx, ny]) => this.getIndex(nx, ny) !== -1 ? grid_data[this.getIndex(nx, ny)] : false);
mine.mine_count = neighbors.filter(Boolean).length;
}
mine.reveal = this.reveal.bind(this, mine);
mine.appendChild(document.createTextNode(this.emojiset[3]));
return mine;
}
reveal(mine, won = false) {
const emoji = mine.isBomb ? (won ? this.emojiset[2] : this.emojiset[1]) : this.numbermoji[mine.mine_count];
const text = mine.isBomb ? (won ? "Bomb discovered" : "Boom!") : (mine.mine_count === 0 ? "Empty field" : `${mine.mine_count} bombs nearby`);
mine.innerHTML = emoji;
mine.setAttribute('aria-label', text);
mine.isMasked = false;
mine.classList.add('unmasked');
if (mine.mine_count === 0 && !mine.isBomb) {
this.revealNeighbors(mine);
}
}
getIndex(x, y) {
return x > this.cols || x <= 0 || y > this.rows || y <= 0 ? -1 : this.cols * (y - 1) + (x - 1);
}
revealNeighbors(mine) {
const x = parseInt(mine.classList[1].substring(1));
const y = parseInt(mine.classList[2].substring(1));
const neighbors_coords = this.getNeighbors(x, y);
neighbors_coords.forEach(([nx, ny]) => {
const neighbor = document.querySelector(`.x${nx}.y${ny}`);
if (neighbor && neighbor.isMasked && !neighbor.isFlagged) {
neighbor.reveal();
}
});
}
bindEvents() {
if (!isBrowser) return;
Array.from(document.getElementsByClassName('cell')).forEach(cell => {
cell.addEventListener('click', evt => this.onClick(cell, evt));
cell.addEventListener('dblclick', () => this.onDoubleClick(cell));
cell.addEventListener('contextmenu', evt => this.onRightClick(cell, evt));
if (iDevice) {
cell.addEventListener('touchstart', () => {
this.holding = setTimeout(() => cell.dispatchEvent(new Event('contextmenu')), 500);
});
cell.addEventListener('touchend', () => clearTimeout(this.holding));
}
});
window.addEventListener('keydown', evt => {
if (evt.key === 'r' || evt.key === 'R') {
this.restart();
}
});
}
onClick(cell, evt) {
if (!cell.isMasked || cell.isFlagged) return;
if (document.getElementsByClassName('unmasked').length === 0) {
this.startTimer();
}
if (cell.isBomb) {
this.reveal(cell);
this.checkGameStatus();
return;
}
cell.reveal();
this.updateFeedback(cell.getAttribute('aria-label'));
if (cell.mine_count === 0 && !cell.isBomb) this.revealNeighbors(cell);
this.checkGameStatus();
}
onDoubleClick(cell) {
if (cell.isFlagged) return;
this.moveIt();
cell.reveal();
this.revealNeighbors(cell);
this.checkGameStatus();
}
onRightClick(cell, evt) {
evt.preventDefault();
if (!cell.isMasked) return;
cell.isFlagged = !cell.isFlagged;
const emoji = cell.isFlagged ? this.emojiset[2] : this.emojiset[3];
const label = cell.isFlagged ? 'Flagged as potential bomb' : 'Field';
cell.innerHTML = emoji;
cell.setAttribute('aria-label', label);
this.updateFeedback(cell.isFlagged ? 'Flagged as potential bomb' : 'Unflagged as potential bomb');
this.updateBombsLeft();
}
checkGameStatus() {
const cells = document.getElementsByClassName('cell');
const masked = Array.from(cells).filter(cell => cell.isMasked);
const bombs = Array.from(cells).filter(cell => cell.isBomb && !cell.isMasked);
if (bombs.length > 0) {
masked.forEach(cell => cell.reveal());
this.endGame('lost');
} else if (masked.length === this.number_of_bombs) {
masked.forEach(cell => cell.reveal(true));
this.endGame('won');
}
}
endGame(result) {
this.result = result;
this.showMessage();
clearInterval(this.timer);
}
bomb_array() {
const data = Array(this.number_of_cells).fill(false);
for (let i = 0; i < this.number_of_bombs; i++) data[i] = true;
return data.sort(() => Math.random() - 0.5);
}
resetMetadata() {
document.getElementById('timer').textContent = '0.00';
document.getElementById('bombs').textContent = this.number_of_bombs;
document.getElementById('moves').textContent = '0';
document.querySelector('.wrapper').classList.remove('won', 'lost');
document.querySelector('.result-emoji').textContent = '';
document.querySelector('.default-emoji').textContent = 'Cuckoosweepers 🐦‍⬛🧹';
document.querySelector('.js-settings').style.display = 'none';
}
updateBombsLeft() {
const bombsLeft = this.number_of_bombs - document.querySelectorAll('.cell[aria-label="Flagged as potential bomb"]').length;
document.getElementById('bombs').textContent = bombsLeft;
}
updateFeedback(text) {
if (feedback) {
feedback.textContent = text;
}
}
prepareEmoji() {
}
restart() {
clearInterval(this.timer);
this.timer = null;
this.result = null;
this.init();
}
showMessage() {
const wrapper = document.querySelector('.wrapper');
const resultEmoji = document.querySelector('.result-emoji');
wrapper.classList.add(this.result);
resultEmoji.textContent = this.result === 'won' ? 'You won!!! 😎' : 'You lost... 😵';
}
moveIt() {
this.moves = (this.moves || 0) + 1;
document.getElementById('moves').textContent = this.moves;
}
startTimer() {
if (this.timer) return;
this.startTime = new Date();
this.timer = setInterval(() => {
const elapsed = ((new Date() - this.startTime) / 1000).toFixed(2);
document.getElementById('timer').textContent = elapsed;
}, 100);
}
}
// document.addEventListener('DOMContentLoaded', () => {
// const emojiSet = ['🐣', '🐦‍⬛', '🧹', '⬜'];
// const game = new Game(10, 10, 10, emojiSet);
// });