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

845
mines.css Normal file
View File

@@ -0,0 +1,845 @@
/* ===== CSS VARIABLES ===== */
:root {
/* Main Palette - Elegant & Subdued */
--bg-primary: #0d0d12;
--bg-secondary: #14141c;
--bg-tertiary: #1c1c28;
--bg-card: #1a1a24;
--bg-card-hover: #222230;
/* Text Colors */
--text-primary: #f5f5f7;
--text-secondary: #a0a0b0;
--text-muted: #606070;
/* Accent Colors */
--gold: #c9a962;
--gold-light: #e0c887;
--gold-dark: #a08840;
/* Green - RARE ACCENT (only for wins/success) */
--green-accent: #2d9469;
--green-light: #3db87f;
--green-glow: rgba(45, 148, 105, 0.3);
/* Danger */
--red: #c44545;
--red-light: #e05555;
--red-glow: rgba(196, 69, 69, 0.4);
/* UI Elements */
--border-color: #2a2a3a;
--border-light: #3a3a4a;
/* Tile States */
--tile-bg: linear-gradient(145deg, #252535, #1e1e2a);
--tile-hover: linear-gradient(145deg, #2a2a3c, #232332);
--tile-revealed-safe: linear-gradient(145deg, #1a2e25, #152520);
--tile-revealed-mine: linear-gradient(145deg, #2e1a1a, #251515);
/* Shadows */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
--shadow-gold: 0 0 20px rgba(201, 169, 98, 0.2);
/* Typography */
--font-display: 'Playfair Display', Georgia, serif;
--font-body: 'Inter', -apple-system, sans-serif;
/* Sizing */
--grid-size: 5;
--tile-size: 70px;
--tile-gap: 8px;
/* Transitions */
--transition-fast: 0.15s ease;
--transition-normal: 0.25s ease;
--transition-slow: 0.4s ease;
}
/* ===== RESET & BASE ===== */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-body);
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/* ===== LAYOUT ===== */
.casino-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
}
/* ===== HEADER ===== */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 30px;
background: var(--bg-secondary);
border-radius: 16px;
border: 1px solid var(--border-color);
margin-bottom: 24px;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
}
.logo-icon {
font-size: 28px;
color: var(--gold);
text-shadow: var(--shadow-gold);
}
.logo-text {
font-family: var(--font-display);
font-size: 28px;
font-weight: 700;
letter-spacing: 4px;
background: linear-gradient(135deg, var(--gold-light), var(--gold));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.balance-display {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.balance-label {
font-size: 12px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
}
.balance-value {
font-size: 24px;
font-weight: 600;
color: var(--gold);
}
/* ===== MAIN GAME AREA ===== */
.game-area {
display: grid;
grid-template-columns: 280px 1fr 240px;
gap: 24px;
align-items: start;
}
/* ===== PANELS ===== */
.controls-panel,
.history-panel {
background: var(--bg-secondary);
border-radius: 16px;
border: 1px solid var(--border-color);
padding: 24px;
}
.panel-section {
margin-bottom: 24px;
padding-bottom: 24px;
border-bottom: 1px solid var(--border-color);
}
.panel-section:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.panel-title {
font-family: var(--font-display);
font-size: 16px;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 16px;
text-transform: uppercase;
letter-spacing: 2px;
}
/* ===== INPUT STYLES ===== */
.input-label {
display: block;
font-size: 12px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 10px;
}
.bet-input-group {
display: flex;
align-items: center;
background: var(--bg-tertiary);
border-radius: 10px;
border: 1px solid var(--border-color);
overflow: hidden;
transition: border-color var(--transition-fast);
}
.bet-input-group:focus-within {
border-color: var(--gold-dark);
}
.currency-symbol {
padding: 12px;
color: var(--text-muted);
font-weight: 500;
}
.bet-input {
flex: 1;
background: transparent;
border: none;
color: var(--text-primary);
font-size: 18px;
font-weight: 600;
padding: 12px 8px;
outline: none;
width: 100%;
}
.bet-input::-webkit-outer-spin-button,
.bet-input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.bet-buttons {
display: flex;
border-left: 1px solid var(--border-color);
}
.bet-btn {
background: transparent;
border: none;
color: var(--text-secondary);
padding: 12px 14px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all var(--transition-fast);
}
.bet-btn:hover {
background: var(--bg-card-hover);
color: var(--gold);
}
.bet-btn:first-child {
border-right: 1px solid var(--border-color);
}
/* ===== MINES SELECTOR ===== */
.mines-selector {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-bottom: 16px;
}
.mines-btn {
width: 40px;
height: 40px;
border-radius: 10px;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
color: var(--text-primary);
font-size: 20px;
cursor: pointer;
transition: all var(--transition-fast);
}
.mines-btn:hover {
background: var(--bg-card-hover);
border-color: var(--gold-dark);
color: var(--gold);
}
.mines-btn:active {
transform: scale(0.95);
}
.mines-value {
font-size: 32px;
font-weight: 700;
color: var(--gold);
min-width: 50px;
text-align: center;
}
.mines-range {
display: flex;
align-items: center;
gap: 12px;
font-size: 12px;
color: var(--text-muted);
}
.mines-range input[type="range"] {
flex: 1;
-webkit-appearance: none;
appearance: none;
height: 4px;
background: var(--bg-tertiary);
border-radius: 4px;
cursor: pointer;
}
.mines-range input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: var(--gold);
border-radius: 50%;
cursor: pointer;
transition: transform var(--transition-fast);
}
.mines-range input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
/* ===== STATS SECTION ===== */
.stats-section {
background: var(--bg-tertiary);
border-radius: 10px;
padding: 16px !important;
margin-left: -8px;
margin-right: -8px;
width: calc(100% + 16px);
}
.stat-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
}
.stat-row:not(:last-child) {
border-bottom: 1px solid var(--border-color);
}
.stat-label {
font-size: 13px;
color: var(--text-secondary);
}
.stat-value {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
}
.stat-value.multiplier {
color: var(--gold);
}
/* ===== PAYOUT SECTION ===== */
.payout-section {
text-align: center;
}
.current-multiplier,
.potential-win {
margin-bottom: 16px;
}
.payout-label {
display: block;
font-size: 11px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 6px;
}
.payout-value {
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
}
.payout-value.win-value {
color: var(--gold);
}
/* ===== ACTION BUTTONS ===== */
.action-section {
border-bottom: none !important;
}
.action-btn {
width: 100%;
padding: 16px 24px;
border-radius: 12px;
border: none;
font-size: 16px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
transition: all var(--transition-normal);
text-transform: uppercase;
letter-spacing: 1px;
}
.start-btn {
background: linear-gradient(135deg, var(--gold), var(--gold-dark));
color: var(--bg-primary);
box-shadow: var(--shadow-gold);
}
.start-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(201, 169, 98, 0.4);
}
.start-btn:active:not(:disabled) {
transform: translateY(0);
}
.start-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.cashout-btn {
background: linear-gradient(135deg, var(--green-accent), #257a55);
color: var(--text-primary);
box-shadow: 0 4px 16px var(--green-glow);
animation: pulse-green 2s infinite;
}
.cashout-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px var(--green-glow);
}
@keyframes pulse-green {
0%, 100% { box-shadow: 0 4px 16px var(--green-glow); }
50% { box-shadow: 0 4px 24px rgba(45, 148, 105, 0.5); }
}
.btn-icon {
font-size: 14px;
}
/* ===== GAME GRID ===== */
.grid-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.grid-wrapper {
position: relative;
padding: 24px;
background: var(--bg-secondary);
border-radius: 20px;
border: 1px solid var(--border-color);
}
.game-grid {
display: grid;
grid-template-columns: repeat(var(--grid-size), var(--tile-size));
grid-template-rows: repeat(var(--grid-size), var(--tile-size));
gap: var(--tile-gap);
}
.tile {
width: var(--tile-size);
height: var(--tile-size);
background: var(--tile-bg);
border-radius: 12px;
border: 1px solid var(--border-color);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
transition: all var(--transition-normal);
position: relative;
overflow: hidden;
}
.tile::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.05), transparent);
border-radius: 12px;
}
.tile:hover:not(.revealed):not(.disabled) {
background: var(--tile-hover);
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--border-light);
}
.tile:active:not(.revealed):not(.disabled) {
transform: translateY(-1px);
}
.tile.disabled {
cursor: not-allowed;
opacity: 0.7;
}
.tile.revealed {
cursor: default;
transform: none;
}
.tile.revealed.safe {
background: var(--tile-revealed-safe);
border-color: var(--green-accent);
animation: reveal-safe 0.4s ease;
}
.tile.revealed.safe .tile-icon {
color: var(--green-light);
text-shadow: 0 0 10px var(--green-glow);
}
.tile.revealed.mine {
background: var(--tile-revealed-mine);
border-color: var(--red);
animation: reveal-mine 0.5s ease;
}
.tile.revealed.mine .tile-icon {
color: var(--red-light);
text-shadow: 0 0 10px var(--red-glow);
}
.tile.show-mine {
background: var(--tile-revealed-mine);
border-color: rgba(196, 69, 69, 0.5);
}
.tile.show-mine .tile-icon {
color: var(--red);
opacity: 0.7;
}
@keyframes reveal-safe {
0% { transform: scale(0.8); opacity: 0; }
50% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
}
@keyframes reveal-mine {
0% { transform: scale(0.8) rotate(-10deg); opacity: 0; }
30% { transform: scale(1.2) rotate(5deg); }
60% { transform: scale(0.95) rotate(-2deg); }
100% { transform: scale(1) rotate(0); opacity: 1; }
}
.tile-icon {
position: relative;
z-index: 1;
}
/* Grid Overlay */
.grid-overlay {
position: absolute;
inset: 0;
background: rgba(13, 13, 18, 0.9);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
.overlay-content {
text-align: center;
}
.overlay-icon {
font-size: 48px;
display: block;
margin-bottom: 16px;
}
.overlay-text {
font-size: 16px;
color: var(--text-secondary);
}
.revealed-counter {
font-size: 14px;
color: var(--text-muted);
text-align: center;
}
/* ===== HISTORY PANEL ===== */
.history-panel {
max-height: 600px;
overflow-y: auto;
}
.history-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.history-empty {
color: var(--text-muted);
font-size: 14px;
text-align: center;
padding: 20px;
}
.history-item {
background: var(--bg-tertiary);
border-radius: 10px;
padding: 14px;
border-left: 3px solid var(--border-color);
transition: all var(--transition-fast);
}
.history-item:hover {
background: var(--bg-card-hover);
}
.history-item.win {
border-left-color: var(--green-accent);
}
.history-item.loss {
border-left-color: var(--red);
}
.history-bet {
font-size: 12px;
color: var(--text-muted);
margin-bottom: 4px;
}
.history-result {
font-size: 16px;
font-weight: 600;
}
.history-item.win .history-result {
color: var(--green-light);
}
.history-item.loss .history-result {
color: var(--red-light);
}
.history-details {
font-size: 11px;
color: var(--text-muted);
margin-top: 6px;
}
/* ===== MODAL ===== */
.modal {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(8px);
animation: modal-fade-in 0.3s ease;
}
@keyframes modal-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background: var(--bg-secondary);
border-radius: 24px;
padding: 48px;
text-align: center;
border: 1px solid var(--border-color);
box-shadow: var(--shadow-lg);
animation: modal-scale-in 0.4s ease;
min-width: 320px;
}
.modal-content.win {
border-color: var(--green-accent);
box-shadow: 0 0 60px var(--green-glow);
}
.modal-content.loss {
border-color: var(--red);
box-shadow: 0 0 60px var(--red-glow);
}
@keyframes modal-scale-in {
from { transform: scale(0.8); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.modal-icon {
font-size: 64px;
margin-bottom: 20px;
display: block;
}
.modal-title {
font-family: var(--font-display);
font-size: 28px;
font-weight: 700;
margin-bottom: 16px;
}
.modal-content.win .modal-title {
color: var(--green-light);
}
.modal-content.loss .modal-title {
color: var(--red-light);
}
.modal-amount {
font-size: 42px;
font-weight: 700;
margin-bottom: 8px;
}
.modal-content.win .modal-amount {
color: var(--green-light);
}
.modal-content.loss .modal-amount {
color: var(--red-light);
}
.modal-subtitle {
font-size: 14px;
color: var(--text-muted);
margin-bottom: 32px;
}
.modal-btn {
background: linear-gradient(135deg, var(--gold), var(--gold-dark));
color: var(--bg-primary);
border: none;
padding: 14px 40px;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-normal);
text-transform: uppercase;
letter-spacing: 1px;
}
.modal-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-gold);
}
/* ===== UTILITIES ===== */
.hidden {
display: none !important;
}
/* ===== SCROLLBAR ===== */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: var(--bg-tertiary);
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: var(--border-light);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* ===== RESPONSIVE ===== */
@media (max-width: 1200px) {
.game-area {
grid-template-columns: 260px 1fr;
}
.history-panel {
display: none;
}
}
@media (max-width: 900px) {
.game-area {
grid-template-columns: 1fr;
}
.controls-panel {
order: 2;
}
.grid-container {
order: 1;
}
:root {
--tile-size: 60px;
--tile-gap: 6px;
}
}
@media (max-width: 500px) {
:root {
--tile-size: 50px;
--tile-gap: 5px;
}
.casino-container {
padding: 12px;
}
.header {
padding: 16px 20px;
}
.logo-text {
font-size: 22px;
}
.grid-wrapper {
padding: 16px;
}
}

133
mines.html Normal file
View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mines - Elegant Casino</title>
<link rel="stylesheet" href="mines.css">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
</head>
<body>
<div class="casino-container">
<!-- Header -->
<header class="header">
<div class="logo">
<span class="logo-icon"></span>
<span class="logo-text">MINES</span>
</div>
<div class="balance-display">
<span class="balance-label">Balance</span>
<span class="balance-value" id="balance">$1,000.00</span>
</div>
</header>
<!-- Main Game Area -->
<main class="game-area">
<!-- Left Panel - Controls -->
<aside class="controls-panel">
<div class="panel-section">
<label class="input-label">Bet Amount</label>
<div class="bet-input-group">
<span class="currency-symbol">$</span>
<input type="number" id="bet-amount" class="bet-input" value="10" min="1" step="1">
<div class="bet-buttons">
<button class="bet-btn" data-action="half">½</button>
<button class="bet-btn" data-action="double">2×</button>
</div>
</div>
</div>
<div class="panel-section">
<label class="input-label">Number of Mines</label>
<div class="mines-selector">
<button class="mines-btn" id="mines-decrease"></button>
<span class="mines-value" id="mines-count">3</span>
<button class="mines-btn" id="mines-increase">+</button>
</div>
<div class="mines-range">
<span>1</span>
<input type="range" id="mines-slider" min="1" max="24" value="3">
<span>24</span>
</div>
</div>
<div class="panel-section stats-section">
<div class="stat-row">
<span class="stat-label">Tiles</span>
<span class="stat-value" id="tiles-info">25 (5×5)</span>
</div>
<div class="stat-row">
<span class="stat-label">Safe Tiles</span>
<span class="stat-value" id="safe-tiles">22</span>
</div>
<div class="stat-row">
<span class="stat-label">Next Multiplier</span>
<span class="stat-value multiplier" id="next-multiplier">×1.14</span>
</div>
</div>
<div class="panel-section payout-section">
<div class="current-multiplier">
<span class="payout-label">Current Multiplier</span>
<span class="payout-value" id="current-multiplier">×1.00</span>
</div>
<div class="potential-win">
<span class="payout-label">Potential Win</span>
<span class="payout-value win-value" id="potential-win">$0.00</span>
</div>
</div>
<div class="panel-section action-section">
<button class="action-btn start-btn" id="start-btn">
<span class="btn-icon"></span>
Start Round
</button>
<button class="action-btn cashout-btn hidden" id="cashout-btn">
<span class="btn-icon">$</span>
Cash Out
</button>
</div>
</aside>
<!-- Center - Game Grid -->
<section class="grid-container">
<div class="grid-wrapper">
<div class="game-grid" id="game-grid">
<!-- Tiles will be generated by JS -->
</div>
<div class="grid-overlay hidden" id="grid-overlay">
<div class="overlay-content">
<span class="overlay-icon" id="overlay-icon">💎</span>
<span class="overlay-text" id="overlay-text">Place your bet and start!</span>
</div>
</div>
</div>
<div class="revealed-counter">
<span id="revealed-count">0</span> / <span id="total-safe">22</span> tiles revealed
</div>
</section>
<!-- Right Panel - History -->
<aside class="history-panel">
<h3 class="panel-title">Round History</h3>
<div class="history-list" id="history-list">
<div class="history-empty">No rounds played yet</div>
</div>
</aside>
</main>
<!-- Result Modal -->
<div class="modal hidden" id="result-modal">
<div class="modal-content" id="modal-content">
<div class="modal-icon" id="modal-icon">💎</div>
<h2 class="modal-title" id="modal-title">You Won!</h2>
<p class="modal-amount" id="modal-amount">$125.00</p>
<p class="modal-subtitle" id="modal-subtitle">×2.50 multiplier</p>
<button class="modal-btn" id="modal-close">Continue</button>
</div>
</div>
</div>
<script src="mines.js"></script>
</body>
</html>

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