initial commit
This commit is contained in:
384
mines.js
Normal file
384
mines.js
Normal file
@@ -0,0 +1,384 @@
|
||||
/**
|
||||
* MINES - Casino Game
|
||||
* Elegant browser-based Mines game simulation
|
||||
*/
|
||||
|
||||
class MinesGame {
|
||||
constructor(config = {}) {
|
||||
// Grid configuration (easily changeable)
|
||||
this.gridSize = config.gridSize || 5;
|
||||
this.totalTiles = this.gridSize * this.gridSize;
|
||||
|
||||
// Game state
|
||||
this.balance = 1000;
|
||||
this.bet = 10;
|
||||
this.minesCount = 3;
|
||||
this.isPlaying = false;
|
||||
this.revealedCount = 0;
|
||||
this.currentMultiplier = 1.0;
|
||||
this.minePositions = [];
|
||||
this.history = [];
|
||||
|
||||
// DOM Elements
|
||||
this.elements = {
|
||||
balance: document.getElementById('balance'),
|
||||
betInput: document.getElementById('bet-amount'),
|
||||
minesCount: document.getElementById('mines-count'),
|
||||
minesSlider: document.getElementById('mines-slider'),
|
||||
minesDecrease: document.getElementById('mines-decrease'),
|
||||
minesIncrease: document.getElementById('mines-increase'),
|
||||
tilesInfo: document.getElementById('tiles-info'),
|
||||
safeTiles: document.getElementById('safe-tiles'),
|
||||
nextMultiplier: document.getElementById('next-multiplier'),
|
||||
currentMultiplier: document.getElementById('current-multiplier'),
|
||||
potentialWin: document.getElementById('potential-win'),
|
||||
startBtn: document.getElementById('start-btn'),
|
||||
cashoutBtn: document.getElementById('cashout-btn'),
|
||||
gameGrid: document.getElementById('game-grid'),
|
||||
gridOverlay: document.getElementById('grid-overlay'),
|
||||
overlayIcon: document.getElementById('overlay-icon'),
|
||||
overlayText: document.getElementById('overlay-text'),
|
||||
revealedCount: document.getElementById('revealed-count'),
|
||||
totalSafe: document.getElementById('total-safe'),
|
||||
historyList: document.getElementById('history-list'),
|
||||
modal: document.getElementById('result-modal'),
|
||||
modalContent: document.getElementById('modal-content'),
|
||||
modalIcon: document.getElementById('modal-icon'),
|
||||
modalTitle: document.getElementById('modal-title'),
|
||||
modalAmount: document.getElementById('modal-amount'),
|
||||
modalSubtitle: document.getElementById('modal-subtitle'),
|
||||
modalClose: document.getElementById('modal-close')
|
||||
};
|
||||
|
||||
// Initialize
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.updateGridCSS();
|
||||
this.createGrid();
|
||||
this.bindEvents();
|
||||
this.updateUI();
|
||||
this.showOverlay('💎', 'Place your bet and start!');
|
||||
}
|
||||
|
||||
updateGridCSS() {
|
||||
document.documentElement.style.setProperty('--grid-size', this.gridSize);
|
||||
this.elements.tilesInfo.textContent = `${this.totalTiles} (${this.gridSize}×${this.gridSize})`;
|
||||
}
|
||||
|
||||
createGrid() {
|
||||
this.elements.gameGrid.innerHTML = '';
|
||||
for (let i = 0; i < this.totalTiles; i++) {
|
||||
const tile = document.createElement('div');
|
||||
tile.className = 'tile disabled';
|
||||
tile.dataset.index = i;
|
||||
tile.innerHTML = '<span class="tile-icon"></span>';
|
||||
this.elements.gameGrid.appendChild(tile);
|
||||
}
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// Bet input
|
||||
this.elements.betInput.addEventListener('input', (e) => {
|
||||
this.bet = Math.max(1, parseFloat(e.target.value) || 1);
|
||||
this.updateUI();
|
||||
});
|
||||
|
||||
// Bet buttons (half/double)
|
||||
document.querySelectorAll('.bet-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const action = btn.dataset.action;
|
||||
if (action === 'half') {
|
||||
this.bet = Math.max(1, Math.floor(this.bet / 2));
|
||||
} else if (action === 'double') {
|
||||
this.bet = Math.min(this.balance, this.bet * 2);
|
||||
}
|
||||
this.elements.betInput.value = this.bet;
|
||||
this.updateUI();
|
||||
});
|
||||
});
|
||||
|
||||
// Mines selector
|
||||
this.elements.minesDecrease.addEventListener('click', () => {
|
||||
this.minesCount = Math.max(1, this.minesCount - 1);
|
||||
this.elements.minesSlider.value = this.minesCount;
|
||||
this.updateUI();
|
||||
});
|
||||
|
||||
this.elements.minesIncrease.addEventListener('click', () => {
|
||||
this.minesCount = Math.min(this.totalTiles - 1, this.minesCount + 1);
|
||||
this.elements.minesSlider.value = this.minesCount;
|
||||
this.updateUI();
|
||||
});
|
||||
|
||||
this.elements.minesSlider.addEventListener('input', (e) => {
|
||||
this.minesCount = parseInt(e.target.value);
|
||||
this.updateUI();
|
||||
});
|
||||
|
||||
// Update slider max based on grid size
|
||||
this.elements.minesSlider.max = this.totalTiles - 1;
|
||||
|
||||
// Start button
|
||||
this.elements.startBtn.addEventListener('click', () => this.startRound());
|
||||
|
||||
// Cashout button
|
||||
this.elements.cashoutBtn.addEventListener('click', () => this.cashOut());
|
||||
|
||||
// Modal close
|
||||
this.elements.modalClose.addEventListener('click', () => this.hideModal());
|
||||
|
||||
// Tile clicks
|
||||
this.elements.gameGrid.addEventListener('click', (e) => {
|
||||
const tile = e.target.closest('.tile');
|
||||
if (tile && this.isPlaying && !tile.classList.contains('revealed') && !tile.classList.contains('disabled')) {
|
||||
this.revealTile(parseInt(tile.dataset.index));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate multiplier based on mines count and revealed tiles
|
||||
* Formula: Each safe reveal increases multiplier based on probability
|
||||
* The fewer safe tiles remaining, the higher the reward
|
||||
*/
|
||||
calculateMultiplier(revealed) {
|
||||
if (revealed === 0) return 1.0;
|
||||
|
||||
const safeTiles = this.totalTiles - this.minesCount;
|
||||
let multiplier = 1.0;
|
||||
|
||||
// House edge (around 3%)
|
||||
const houseEdge = 0.97;
|
||||
|
||||
for (let i = 0; i < revealed; i++) {
|
||||
const remainingTiles = this.totalTiles - i;
|
||||
const remainingSafe = safeTiles - i;
|
||||
// Fair odds * house edge
|
||||
const stepMultiplier = (remainingTiles / remainingSafe) * houseEdge;
|
||||
multiplier *= stepMultiplier;
|
||||
}
|
||||
|
||||
return Math.round(multiplier * 100) / 100;
|
||||
}
|
||||
|
||||
getNextMultiplier() {
|
||||
return this.calculateMultiplier(this.revealedCount + 1);
|
||||
}
|
||||
|
||||
updateUI() {
|
||||
// Update mines count display
|
||||
this.elements.minesCount.textContent = this.minesCount;
|
||||
|
||||
// Update safe tiles count
|
||||
const safeTiles = this.totalTiles - this.minesCount;
|
||||
this.elements.safeTiles.textContent = safeTiles;
|
||||
this.elements.totalSafe.textContent = safeTiles;
|
||||
|
||||
// Update multipliers
|
||||
this.elements.currentMultiplier.textContent = `×${this.currentMultiplier.toFixed(2)}`;
|
||||
this.elements.nextMultiplier.textContent = `×${this.getNextMultiplier().toFixed(2)}`;
|
||||
|
||||
// Update potential win
|
||||
const potentialWin = this.bet * this.currentMultiplier;
|
||||
this.elements.potentialWin.textContent = this.formatCurrency(potentialWin);
|
||||
|
||||
// Update revealed count
|
||||
this.elements.revealedCount.textContent = this.revealedCount;
|
||||
|
||||
// Update balance
|
||||
this.elements.balance.textContent = this.formatCurrency(this.balance);
|
||||
|
||||
// Update start button state
|
||||
this.elements.startBtn.disabled = this.bet > this.balance || this.bet <= 0;
|
||||
}
|
||||
|
||||
formatCurrency(amount) {
|
||||
return '$' + amount.toLocaleString('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
}
|
||||
|
||||
showOverlay(icon, text) {
|
||||
this.elements.overlayIcon.textContent = icon;
|
||||
this.elements.overlayText.textContent = text;
|
||||
this.elements.gridOverlay.classList.remove('hidden');
|
||||
}
|
||||
|
||||
hideOverlay() {
|
||||
this.elements.gridOverlay.classList.add('hidden');
|
||||
}
|
||||
|
||||
generateMinePositions() {
|
||||
const positions = [];
|
||||
while (positions.length < this.minesCount) {
|
||||
const pos = Math.floor(Math.random() * this.totalTiles);
|
||||
if (!positions.includes(pos)) {
|
||||
positions.push(pos);
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
startRound() {
|
||||
if (this.bet > this.balance) return;
|
||||
|
||||
// Deduct bet from balance
|
||||
this.balance -= this.bet;
|
||||
|
||||
// Reset game state
|
||||
this.isPlaying = true;
|
||||
this.revealedCount = 0;
|
||||
this.currentMultiplier = 1.0;
|
||||
this.minePositions = this.generateMinePositions();
|
||||
|
||||
// Update UI
|
||||
this.hideOverlay();
|
||||
this.elements.startBtn.classList.add('hidden');
|
||||
this.elements.cashoutBtn.classList.remove('hidden');
|
||||
|
||||
// Reset all tiles
|
||||
document.querySelectorAll('.tile').forEach(tile => {
|
||||
tile.className = 'tile';
|
||||
tile.querySelector('.tile-icon').textContent = '';
|
||||
});
|
||||
|
||||
// Disable bet controls during game
|
||||
this.elements.betInput.disabled = true;
|
||||
this.elements.minesSlider.disabled = true;
|
||||
this.elements.minesDecrease.disabled = true;
|
||||
this.elements.minesIncrease.disabled = true;
|
||||
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
revealTile(index) {
|
||||
const tile = document.querySelectorAll('.tile')[index];
|
||||
const icon = tile.querySelector('.tile-icon');
|
||||
|
||||
if (this.minePositions.includes(index)) {
|
||||
// Hit a mine - GAME OVER
|
||||
tile.classList.add('revealed', 'mine');
|
||||
icon.textContent = '💣';
|
||||
this.endRound(false);
|
||||
} else {
|
||||
// Safe tile
|
||||
tile.classList.add('revealed', 'safe');
|
||||
icon.textContent = '💎';
|
||||
this.revealedCount++;
|
||||
this.currentMultiplier = this.calculateMultiplier(this.revealedCount);
|
||||
this.updateUI();
|
||||
|
||||
// Check if all safe tiles are revealed
|
||||
const safeTiles = this.totalTiles - this.minesCount;
|
||||
if (this.revealedCount >= safeTiles) {
|
||||
this.cashOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cashOut() {
|
||||
if (!this.isPlaying) return;
|
||||
|
||||
const winnings = this.bet * this.currentMultiplier;
|
||||
this.balance += winnings;
|
||||
this.endRound(true, winnings);
|
||||
}
|
||||
|
||||
endRound(isWin, winnings = 0) {
|
||||
this.isPlaying = false;
|
||||
|
||||
// Reveal all mines
|
||||
this.minePositions.forEach(pos => {
|
||||
const tile = document.querySelectorAll('.tile')[pos];
|
||||
if (!tile.classList.contains('revealed')) {
|
||||
tile.classList.add('show-mine');
|
||||
tile.querySelector('.tile-icon').textContent = '💣';
|
||||
}
|
||||
});
|
||||
|
||||
// Disable all tiles
|
||||
document.querySelectorAll('.tile').forEach(tile => {
|
||||
tile.classList.add('disabled');
|
||||
});
|
||||
|
||||
// Re-enable controls
|
||||
this.elements.betInput.disabled = false;
|
||||
this.elements.minesSlider.disabled = false;
|
||||
this.elements.minesDecrease.disabled = false;
|
||||
this.elements.minesIncrease.disabled = false;
|
||||
|
||||
// Update buttons
|
||||
this.elements.cashoutBtn.classList.add('hidden');
|
||||
this.elements.startBtn.classList.remove('hidden');
|
||||
|
||||
// Add to history
|
||||
this.addToHistory(isWin, winnings);
|
||||
|
||||
// Show result modal
|
||||
this.showResultModal(isWin, winnings);
|
||||
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
showResultModal(isWin, winnings) {
|
||||
this.elements.modalContent.className = 'modal-content ' + (isWin ? 'win' : 'loss');
|
||||
|
||||
if (isWin) {
|
||||
this.elements.modalIcon.textContent = '💎';
|
||||
this.elements.modalTitle.textContent = 'You Won!';
|
||||
this.elements.modalAmount.textContent = '+' + this.formatCurrency(winnings);
|
||||
this.elements.modalSubtitle.textContent = `×${this.currentMultiplier.toFixed(2)} multiplier • ${this.revealedCount} tiles revealed`;
|
||||
} else {
|
||||
this.elements.modalIcon.textContent = '💣';
|
||||
this.elements.modalTitle.textContent = 'Mine Hit!';
|
||||
this.elements.modalAmount.textContent = '-' + this.formatCurrency(this.bet);
|
||||
this.elements.modalSubtitle.textContent = `${this.revealedCount} tiles revealed before explosion`;
|
||||
}
|
||||
|
||||
this.elements.modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
hideModal() {
|
||||
this.elements.modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
addToHistory(isWin, winnings) {
|
||||
const historyItem = {
|
||||
isWin,
|
||||
bet: this.bet,
|
||||
winnings: isWin ? winnings : -this.bet,
|
||||
multiplier: this.currentMultiplier,
|
||||
mines: this.minesCount,
|
||||
revealed: this.revealedCount
|
||||
};
|
||||
|
||||
this.history.unshift(historyItem);
|
||||
if (this.history.length > 20) this.history.pop();
|
||||
|
||||
this.renderHistory();
|
||||
}
|
||||
|
||||
renderHistory() {
|
||||
if (this.history.length === 0) {
|
||||
this.elements.historyList.innerHTML = '<div class="history-empty">No rounds played yet</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
this.elements.historyList.innerHTML = this.history.map(item => `
|
||||
<div class="history-item ${item.isWin ? 'win' : 'loss'}">
|
||||
<div class="history-bet">Bet: ${this.formatCurrency(item.bet)}</div>
|
||||
<div class="history-result">${item.isWin ? '+' : ''}${this.formatCurrency(item.winnings)}</div>
|
||||
<div class="history-details">×${item.multiplier.toFixed(2)} • ${item.mines} mines • ${item.revealed} revealed</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize game when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// You can change grid size here: 5, 6, 7, etc.
|
||||
window.minesGame = new MinesGame({ gridSize: 5 });
|
||||
});
|
||||
Reference in New Issue
Block a user