initial commit

This commit is contained in:
2026-02-15 10:58:58 +01:00
commit 6edd2637e6
3 changed files with 1362 additions and 0 deletions

384
mines.js Normal file
View 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 });
});