sapbot

Single File Minesweeper

May 2nd, 2026
36
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 12.60 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Single File Minesweeper</title>
  7.     <style>
  8.         :root {
  9.             --bg-color: #c0c0c0;
  10.             --border-light: #ffffff;
  11.             --border-dark: #808080;
  12.             --cell-hidden: #c0c0c0;
  13.             --cell-revealed: #c0c0c0;
  14.             --cell-explosion: #ff0000;
  15.         }
  16.  
  17.         body {
  18.             font-family: 'Courier New', Courier, monospace;
  19.             background-color: #2c3e50;
  20.             display: flex;
  21.             justify-content: center;
  22.             align-items: center;
  23.             height: 100vh;
  24.             margin: 0;
  25.             user-select: none;
  26.         }
  27.  
  28.         .game-container {
  29.             background-color: var(--bg-color);
  30.             padding: 10px;
  31.             border: 3px solid;
  32.             border-color: var(--border-light) var(--border-dark) var(--border-dark) var(--border-light);
  33.             box-shadow: 5px 5px 15px rgba(0,0,0,0.5);
  34.             display: flex;
  35.             flex-direction: column;
  36.             gap: 10px;
  37.         }
  38.  
  39.         /* Header Controls */
  40.         .header {
  41.             display: flex;
  42.             justify-content: space-between;
  43.             align-items: center;
  44.             background-color: #c0c0c0;
  45.             padding: 5px;
  46.             border: 2px solid;
  47.             border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
  48.             margin-bottom: 10px;
  49.         }
  50.  
  51.         .counter {
  52.             background: black;
  53.             color: red;
  54.             font-family: 'Courier New', Courier, monospace;
  55.             font-size: 24px;
  56.             padding: 2px 5px;
  57.             border: 1px solid;
  58.             border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
  59.             min-width: 50px;
  60.             text-align: center;
  61.         }
  62.  
  63.         .reset-btn {
  64.             font-size: 20px;
  65.             padding: 0 10px;
  66.             cursor: pointer;
  67.             background-color: var(--bg-color);
  68.             border: 2px solid;
  69.             border-color: var(--border-light) var(--border-dark) var(--border-dark) var(--border-light);
  70.         }
  71.         .reset-btn:active {
  72.             border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
  73.             transform: translate(1px, 1px);
  74.         }
  75.  
  76.         /* Difficulty Selector */
  77.         .controls {
  78.             display: flex;
  79.             justify-content: center;
  80.             gap: 5px;
  81.             margin-bottom: 5px;
  82.         }
  83.        
  84.         select {
  85.             padding: 5px;
  86.             font-family: inherit;
  87.             border: 2px solid;
  88.             border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
  89.             background: #c0c0c0;
  90.         }
  91.  
  92.         /* The Grid */
  93.         #grid {
  94.             display: grid;
  95.             border: 3px solid;
  96.             border-color: var(--border-dark) var(--border-light) var(--border-light) var(--border-dark);
  97.             gap: 1px;
  98.             background-color: var(--border-dark);
  99.         }
  100.  
  101.         .cell {
  102.             width: 25px;
  103.             height: 25px;
  104.             background-color: var(--cell-hidden);
  105.             display: flex;
  106.             justify-content: center;
  107.             align-items: center;
  108.             font-weight: bold;
  109.             font-size: 18px;
  110.             cursor: pointer;
  111.             border: 2px solid;
  112.             border-color: var(--border-light) var(--border-dark) var(--border-dark) var(--border-light);
  113.         }
  114.  
  115.         .cell.revealed {
  116.             background-color: var(--cell-revealed);
  117.             border: 1px solid #999;
  118.         }
  119.  
  120.         .cell.flagged {
  121.             color: red;
  122.         }
  123.  
  124.         .cell.mine {
  125.             background-color: var(--cell-explosion);
  126.             border: 1px solid #999;
  127.         }
  128.  
  129.         /* Number Colors */
  130.         .c1 { color: blue; }
  131.         .c2 { color: green; }
  132.         .c3 { color: red; }
  133.         .c4 { color: darkblue; }
  134.         .c5 { color: brown; }
  135.         .c6 { color: teal; }
  136.         .c7 { color: black; }
  137.         .c8 { color: gray; }
  138.  
  139.     </style>
  140. </head>
  141. <body>
  142.  
  143.     <div class="game-container">
  144.         <div class="controls">
  145.             <select id="difficulty">
  146.                 <option value="beginner">Beginner</option>
  147.                 <option value="intermediate">Intermediate</option>
  148.                 <option value="expert">Expert</option>
  149.             </select>
  150.         </div>
  151.  
  152.         <div class="header">
  153.             <div id="mine-count" class="counter">010</div>
  154.             <button id="reset-btn" class="reset-btn">🙂</button>
  155.             <div id="timer" class="counter">000</div>
  156.         </div>
  157.  
  158.         <div id="grid"></div>
  159.     </div>
  160.  
  161. <script>
  162.     // Game Configuration
  163.     const CONFIG = {
  164.         beginner: { rows: 9, cols: 9, mines: 10 },
  165.         intermediate: { rows: 16, cols: 16, mines: 40 },
  166.         expert: { rows: 16, cols: 30, mines: 99 }
  167.     };
  168.  
  169.     // State Variables
  170.     let currentConfig = CONFIG.beginner;
  171.     let grid = [];
  172.     let isGameOver = false;
  173.     let flags = 0;
  174.     let time = 0;
  175.     let timerInterval = null;
  176.     let cellsRevealed = 0;
  177.     let firstClick = true;
  178.     let isFirstClick = true; // Flag for initialization logic
  179.  
  180.     // DOM Elements
  181.     const gridElement = document.getElementById('grid');
  182.     const mineCountElement = document.getElementById('mine-count');
  183.     const timerElement = document.getElementById('timer');
  184.     const resetBtn = document.getElementById('reset-btn');
  185.     const difficultySelect = document.getElementById('difficulty');
  186.  
  187.     // Initialize Game
  188.     function initGame() {
  189.         // Clear previous interval
  190.         if (timerInterval) clearInterval(timerInterval);
  191.        
  192.         // Reset State
  193.         isGameOver = false;
  194.         isFirstClick = true;
  195.         flags = 0;
  196.         time = 0;
  197.         cellsRevealed = 0;
  198.         firstClick = true;
  199.        
  200.         // Update UI
  201.         updateMineCounter();
  202.         timerElement.innerText = '000';
  203.         resetBtn.innerText = '🙂';
  204.         startTimer();
  205.  
  206.         // Setup Grid
  207.         const { rows, cols } = currentConfig;
  208.         gridElement.innerHTML = '';
  209.         gridElement.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
  210.         gridElement.style.gridTemplateRows = `repeat(${rows}, 1fr)`;
  211.        
  212.         grid = [];
  213.        
  214.         for (let i = 0; i < rows * cols; i++) {
  215.            const cell = document.createElement('div');
  216.            cell.classList.add('cell');
  217.            cell.dataset.index = i;
  218.            
  219.            // Event Listeners
  220.            cell.addEventListener('click', () => handleLeftClick(i));
  221.             cell.addEventListener('contextmenu', (e) => handleRightClick(i, e));
  222.  
  223.             gridElement.appendChild(cell);
  224.            
  225.             grid.push({
  226.                 element: cell,
  227.                 isMine: false,
  228.                 isRevealed: false,
  229.                 isFlagged: false,
  230.                 neighborCount: 0
  231.             });
  232.         }
  233.     }
  234.  
  235.     // Start Timer
  236.     function startTimer() {
  237.         timerInterval = setInterval(() => {
  238.             time++;
  239.             if (time > 999) time = 999;
  240.             timerElement.innerText = time.toString().padStart(3, '0');
  241.         }, 100);
  242.     }
  243.  
  244.     // Place Mines (Ensures first click is safe)
  245.     function placeMines(safeIndex) {
  246.         let minesPlaced = 0;
  247.         const totalCells = grid.length;
  248.        
  249.         while (minesPlaced < currentConfig.mines) {
  250.            const randIndex = Math.floor(Math.random() * totalCells);
  251.            
  252.            // Don't place mine on the first clicked cell or its immediate neighbors
  253.            if (randIndex !== safeIndex && !grid[randIndex].isMine) {
  254.                grid[randIndex].isMine = true;
  255.                minesPlaced++;
  256.            }
  257.        }
  258.        // Calculate neighbor counts
  259.        grid.forEach((cell, index) => {
  260.             if (!cell.isMine) {
  261.                 let count = 0;
  262.                 getNeighbors(index).forEach(neighborIndex => {
  263.                     if (grid[neighborIndex].isMine) count++;
  264.                 });
  265.                 cell.neighborCount = count;
  266.             }
  267.         });
  268.     }
  269.  
  270.     // Get indices of neighbors (8 directions)
  271.     function getNeighbors(index) {
  272.         const row = Math.floor(index / currentConfig.cols);
  273.         const col = index % currentConfig.cols;
  274.         const neighbors = [];
  275.  
  276.         for (let r = row - 1; r <= row + 1; r++) {
  277.            for (let c = col - 1; c <= col + 1; c++) {
  278.                if (r >= 0 && r < currentConfig.rows && c >= 0 && c < currentConfig.cols) {
  279.                    const neighborIndex = r * currentConfig.cols + c;
  280.                     if (neighborIndex !== index) neighbors.push(neighborIndex);
  281.                 }
  282.             }
  283.         }
  284.         return neighbors;
  285.     }
  286.  
  287.     // Handle Left Click
  288.     function handleLeftClick(index) {
  289.         if (isGameOver || grid[index].isFlagged || grid[index].isRevealed) return;
  290.  
  291.         // First Click Logic: Generate board now
  292.         if (isFirstClick) {
  293.             isFirstClick = false;
  294.             placeMines(index);
  295.         }
  296.  
  297.         const cellData = grid[index];
  298.  
  299.         if (cellData.isMine) {
  300.             gameOver(false);
  301.         } else {
  302.             revealCell(index);
  303.             checkWin();
  304.         }
  305.     }
  306.  
  307.     // Handle Right Click (Flag)
  308.     function handleRightClick(index, event) {
  309.         event.preventDefault(); // Stop context menu
  310.        
  311.         if (isGameOver || grid[index].isRevealed) return;
  312.  
  313.         const cellData = grid[index];
  314.         if (cellData.isFlagged) {
  315.             cellData.isFlagged = false;
  316.             cellData.element.classList.remove('flagged');
  317.             flags--;
  318.         } else {
  319.             cellData.isFlagged = true;
  320.             cellData.element.classList.add('flagged');
  321.             flags++;
  322.         }
  323.         updateMineCounter();
  324.     }
  325.  
  326.     // Reveal a single cell
  327.     function revealCell(index) {
  328.         const cellData = grid[index];
  329.         if (cellData.isRevealed || cellData.isFlagged) return;
  330.  
  331.         cellData.isRevealed = true;
  332.         cellsRevealed++;
  333.         cellData.element.classList.add('revealed');
  334.  
  335.         if (cellData.neighborCount > 0) {
  336.             cellData.element.innerText = cellData.neighborCount;
  337.             cellData.element.classList.add(`c${cellData.neighborCount}`);
  338.         } else {
  339.             // Flood fill: If cell is 0, reveal neighbors
  340.             const neighbors = getNeighbors(index);
  341.             neighbors.forEach(neighborIndex => {
  342.                 revealCell(neighborIndex);
  343.             });
  344.         }
  345.     }
  346.  
  347.     // Update Mine Counter
  348.     function updateMineCounter() {
  349.         const remaining = currentConfig.mines - flags;
  350.         mineCountElement.innerText = remaining.toString().padStart(3, '0');
  351.     }
  352.  
  353.     // Check Win Condition
  354.     function checkWin() {
  355.         const totalCells = currentConfig.rows * currentConfig.cols;
  356.         const safeCells = totalCells - currentConfig.mines;
  357.        
  358.         if (cellsRevealed === safeCells) {
  359.             gameOver(true);
  360.         }
  361.     }
  362.  
  363.     // End Game
  364.     function gameOver(won) {
  365.         isGameOver = true;
  366.         clearInterval(timerInterval);
  367.  
  368.         if (won) {
  369.             resetBtn.innerText = '😎';
  370.             // Flag remaining mines visually
  371.             grid.forEach(cell => {
  372.                 if (cell.isMine && !cell.isFlagged) {
  373.                    cell.element.classList.add('flagged');
  374.                 }
  375.             });
  376.         } else {
  377.             resetBtn.innerText = '😵';
  378.             // Reveal all mines
  379.             grid.forEach(cell => {
  380.                 if (cell.isMine) {
  381.                     cell.element.classList.add('mine');
  382.                     cell.element.innerText = '💣';
  383.                 }
  384.             });
  385.             // Highlight the one that killed you
  386.             const clickedIndex = event ? event.target.dataset.index : null;
  387.             // (Optional: Could highlight clicked mine specifically)
  388.         }
  389.     }
  390.  
  391.     // Event Listeners for controls
  392.     resetBtn.addEventListener('click', initGame);
  393.     resetBtn.addEventListener('mousedown', () => { resetBtn.innerText = '😮'; }); // Squeezed face
  394.     resetBtn.addEventListener('mouseup', () => { resetBtn.innerText = '🙂'; });
  395.  
  396.     difficultySelect.addEventListener('change', (e) => {
  397.         currentConfig = CONFIG[e.target.value];
  398.         initGame();
  399.     });
  400.  
  401.     // Start the game on load
  402.     initGame();
  403.  
  404. </script>
  405. </body>
  406. </html>
  407.  
Advertisement
Add Comment
Please, Sign In to add comment