Skip to content

Instantly share code, notes, and snippets.

@intellectronica
Created March 2, 2025 16:53
Show Gist options
  • Save intellectronica/f16780e14602c94538e10acc196c99be to your computer and use it in GitHub Desktop.
Save intellectronica/f16780e14602c94538e10acc196c99be to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<!-- Vibe-coded with GitHub Copilot and Claude 3.7 -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minesweeper</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700&family=Poppins:wght@400;500;600&display=swap');
:root {
--primary-color: #5b63fe;
--secondary-color: #9c6eff;
--bg-color: #f0f2f5;
--cell-bg: #ffffff;
--cell-revealed: #f8f9fa;
--cell-hover: #eef2ff;
--cell-mine: #ff5a5f;
--text-color: #1a1c25;
--border-color: #e2e8f0;
--shadow-sm: 0 2px 8px rgba(0,0,0,0.05);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
--shadow-lg: 0 10px 25px rgba(0,0,0,0.1);
--shadow-inner: inset 0 2px 4px rgba(0,0,0,0.06);
--transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
body {
font-family: 'Poppins', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--bg-color) 0%, #ffffff 100%);
margin: 0;
padding: 40px;
min-height: 100vh;
color: var(--text-color);
}
h1 {
font-family: 'Montserrat', sans-serif;
color: var(--primary-color);
margin-bottom: 30px;
font-weight: 700;
font-size: 3rem;
letter-spacing: -1px;
text-shadow: var(--shadow-sm);
position: relative;
display: inline-block;
}
h1::after {
content: "";
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
height: 4px;
width: 60px;
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
border-radius: 2px;
}
.game-container {
background-color: white;
border-radius: 20px;
padding: 35px;
box-shadow: var(--shadow-lg);
max-width: 90vw;
transition: var(--transition);
position: relative;
overflow: hidden;
}
.game-container::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 5px;
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
}
.game-info {
display: flex;
justify-content: space-between;
margin-bottom: 35px;
width: 100%;
}
.info-box {
background: white;
border: none;
border-radius: 12px;
padding: 15px 25px;
min-width: 110px;
text-align: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.06);
transition: var(--transition);
position: relative;
overflow: hidden;
}
.info-box::before {
content: "";
position: absolute;
top: 0;
left: 0;
height: 4px;
width: 100%;
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
}
.info-box:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0,0,0,0.09);
}
.info-box div:first-child {
font-family: 'Montserrat', sans-serif;
font-size: 0.8rem;
color: #6e7191;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 1.5px;
font-weight: 600;
}
.info-box div:last-child {
font-size: 1.8rem;
font-weight: 700;
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.5px;
}
.game-board {
display: grid;
grid-template-columns: repeat(10, 40px);
grid-template-rows: repeat(10, 40px);
gap: 6px;
border-radius: 16px;
padding: 15px;
background-color: #f7f9fc;
box-shadow: inset 0 2px 10px rgba(0,0,0,0.05);
}
.cell {
width: 40px;
height: 40px;
background-color: var(--cell-bg);
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
font-size: 18px;
cursor: pointer;
user-select: none;
border-radius: 10px;
box-shadow: 0 3px 8px rgba(0,0,0,0.05),
inset 0 -2px 0 rgba(0,0,0,0.07);
transition: var(--transition);
backdrop-filter: blur(5px);
}
.cell:hover {
background-color: var(--cell-hover);
transform: translateY(-2px);
box-shadow: 0 5px 12px rgba(91, 99, 254, 0.15),
inset 0 -3px 0 rgba(0,0,0,0.05);
z-index: 1;
}
.cell.revealed {
background-color: var(--cell-revealed);
box-shadow: var(--shadow-inner);
transform: translateY(0);
}
.cell.mine {
background: radial-gradient(circle, #ff5a5f 0%, #e3242b 100%);
box-shadow: 0 0 15px rgba(255, 90, 95, 0.5);
animation: pulseMine 1.2s infinite;
border: none;
}
.cell.flagged {
position: relative;
overflow: hidden;
}
.cell.flagged::after {
content: "🚩";
font-size: 22px;
animation: flagPlant 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
}
.number-1 { color: #5b63fe; font-weight: 700; text-shadow: 0px 0px 1px rgba(91, 99, 254, 0.2); }
.number-2 { color: #38b000; font-weight: 700; text-shadow: 0px 0px 1px rgba(56, 176, 0, 0.2); }
.number-3 { color: #ff595e; font-weight: 700; text-shadow: 0px 0px 1px rgba(255, 89, 94, 0.2); }
.number-4 { color: #5e60ce; font-weight: 700; text-shadow: 0px 0px 1px rgba(94, 96, 206, 0.2); }
.number-5 { color: #bc4749; font-weight: 700; text-shadow: 0px 0px 1px rgba(188, 71, 73, 0.2); }
.number-6 { color: #00b4d8; font-weight: 700; text-shadow: 0px 0px 1px rgba(0, 180, 216, 0.2); }
.number-7 { color: #7209b7; font-weight: 700; text-shadow: 0px 0px 1px rgba(114, 9, 183, 0.2); }
.number-8 { color: #333333; font-weight: 700; text-shadow: 0px 0px 1px rgba(51, 51, 51, 0.2); }
.controls {
margin-top: 35px;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
width: 100%;
}
button {
padding: 14px 28px;
font-family: 'Montserrat', sans-serif;
font-size: 15px;
font-weight: 600;
border-radius: 12px;
border: none;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
cursor: pointer;
box-shadow: 0 4px 12px rgba(91, 99, 254, 0.25);
transition: var(--transition);
text-transform: uppercase;
letter-spacing: 1px;
position: relative;
overflow: hidden;
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(91, 99, 254, 0.35);
filter: brightness(1.05);
}
button:active {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(91, 99, 254, 0.2);
}
button::after {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: 0.5s;
}
button:hover::after {
left: 100%;
}
#new-game {
width: 100%;
padding: 16px;
font-size: 18px;
font-weight: 700;
}
.difficulty {
display: flex;
gap: 15px;
width: 100%;
}
.difficulty button {
flex: 1;
background: white;
color: var(--text-color);
border: 1px solid var(--border-color);
box-shadow: 0 2px 6px rgba(0,0,0,0.04);
position: relative;
z-index: 1;
overflow: hidden;
}
.difficulty button::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
z-index: -1;
transform: scaleX(0);
transform-origin: 0 50%;
transition: transform 0.5s ease-out;
}
.difficulty button:hover {
color: white;
border: 1px solid transparent;
}
.difficulty button:hover::before {
transform: scaleX(1);
}
@keyframes pulseMine {
0% { transform: scale(1); box-shadow: 0 0 5px rgba(255, 90, 95, 0.5); }
50% { transform: scale(1.05); box-shadow: 0 0 20px rgba(255, 90, 95, 0.8); }
100% { transform: scale(1); box-shadow: 0 0 5px rgba(255, 90, 95, 0.5); }
}
@keyframes flagPlant {
0% { transform: translateY(10px) scale(0.8); opacity: 0; }
70% { transform: translateY(-2px) scale(1.1); }
100% { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes revealCell {
0% { transform: rotateY(90deg); opacity: 0; }
100% { transform: rotateY(0); opacity: 1; }
}
.cell.revealed {
animation: revealCell 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
@media (max-width: 768px) {
.game-container {
padding: 25px;
}
.game-board {
gap: 4px;
padding: 10px;
}
.cell {
width: 35px;
height: 35px;
font-size: 16px;
border-radius: 8px;
}
h1 {
font-size: 2.2rem;
}
.info-box {
padding: 12px 18px;
min-width: 90px;
}
.info-box div:last-child {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<h1>Minesweeper</h1>
<div class="game-container">
<div class="game-info">
<div class="info-box">
<div>Mines</div>
<div id="mines-count">0</div>
</div>
<div class="info-box">
<div>Flags</div>
<div id="flags-count">0</div>
</div>
<div class="info-box">
<div>Time</div>
<div id="timer">0</div>
</div>
</div>
<div class="game-board" id="board"></div>
<div class="controls">
<button id="new-game">New Game</button>
<div class="difficulty">
<button id="easy">Easy</button>
<button id="medium">Medium</button>
<button id="hard">Hard</button>
</div>
</div>
</div>
<script>
// Game configuration
const config = {
easy: { rows: 9, cols: 9, mines: 10 },
medium: { rows: 16, cols: 16, mines: 40 },
hard: { rows: 16, cols: 30, mines: 99 }
};
let currentDifficulty = 'easy';
let board = [];
let gameOver = false;
let timerInterval;
let seconds = 0;
let minesCount = config[currentDifficulty].mines;
let flagsPlaced = 0;
let cellsRevealed = 0;
let totalCells = config[currentDifficulty].rows * config[currentDifficulty].cols;
// DOM elements
const boardElement = document.getElementById('board');
const minesCountElement = document.getElementById('mines-count');
const flagsCountElement = document.getElementById('flags-count');
const timerElement = document.getElementById('timer');
const newGameButton = document.getElementById('new-game');
const easyButton = document.getElementById('easy');
const mediumButton = document.getElementById('medium');
const hardButton = document.getElementById('hard');
// Initialize the game
function initGame() {
// Reset values
board = [];
gameOver = false;
cellsRevealed = 0;
flagsPlaced = 0;
seconds = 0;
// Clear timer
clearInterval(timerInterval);
// Update display
minesCount = config[currentDifficulty].mines;
totalCells = config[currentDifficulty].rows * config[currentDifficulty].cols;
minesCountElement.textContent = minesCount;
flagsCountElement.textContent = flagsPlaced;
timerElement.textContent = '0';
// Generate board
createBoard();
renderBoard();
}
// Create the game board data structure
function createBoard() {
const { rows, cols, mines } = config[currentDifficulty];
// Initialize empty board
for (let i = 0; i < rows; i++) {
board[i] = [];
for (let j = 0; j < cols; j++) {
board[i][j] = {
row: i,
col: j,
isMine: false,
isRevealed: false,
isFlagged: false,
neighborMines: 0
};
}
}
// Place mines randomly
let minesPlaced = 0;
while (minesPlaced < mines) {
const row = Math.floor(Math.random() * rows);
const col = Math.floor(Math.random() * cols);
if (!board[row][col].isMine) {
board[row][col].isMine = true;
minesPlaced++;
}
}
// Calculate neighbor mines
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (!board[i][j].isMine) {
board[i][j].neighborMines = countNeighborMines(i, j);
}
}
}
}
// Count mines around a cell
function countNeighborMines(row, col) {
let count = 0;
const { rows, cols } = config[currentDifficulty];
// Check all surrounding cells
for (let i = Math.max(0, row - 1); i <= Math.min(rows - 1, row + 1); i++) {
for (let j = Math.max(0, col - 1); j <= Math.min(cols - 1, col + 1); j++) {
if (i === row && j === col) continue;
if (board[i][j].isMine) count++;
}
}
return count;
}
// Render the game board on screen
function renderBoard() {
boardElement.innerHTML = '';
const { rows, cols } = config[currentDifficulty];
// Update grid size
boardElement.style.gridTemplateColumns = `repeat(${cols}, 40px)`;
boardElement.style.gridTemplateRows = `repeat(${rows}, 40px)`;
// Create cell elements
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.setAttribute('data-row', i);
cell.setAttribute('data-col', j);
// Add event listeners
cell.addEventListener('click', handleCellClick);
cell.addEventListener('contextmenu', handleCellRightClick);
// Update cell appearance based on state
updateCellAppearance(cell, board[i][j]);
boardElement.appendChild(cell);
}
}
}
// Update a cell's appearance based on its state
function updateCellAppearance(cellElement, cellData) {
if (cellData.isFlagged) {
cellElement.className = 'cell flagged';
return;
}
if (!cellData.isRevealed) {
cellElement.className = 'cell';
cellElement.textContent = '';
return;
}
cellElement.className = 'cell revealed';
if (cellData.isMine) {
cellElement.className += ' mine';
cellElement.textContent = '💣';
} else if (cellData.neighborMines > 0) {
cellElement.textContent = cellData.neighborMines;
}
}
// Handle cell click event
function handleCellClick(event) {
const row = parseInt(event.target.getAttribute('data-row'));
const col = parseInt(event.target.getAttribute('data-col'));
if (gameOver || board[row][col].isRevealed || board[row][col].isFlagged) return;
if (board[row][col].isMine) {
revealAllMines();
gameOver = true;
alert('Game Over! You clicked on a mine.');
return;
}
revealCell(row, col);
if (cellsRevealed === totalCells - minesCount) {
gameOver = true;
alert('Congratulations! You won the game.');
}
}
// Handle cell right-click event
function handleCellRightClick(event) {
event.preventDefault();
const row = parseInt(event.target.getAttribute('data-row'));
const col = parseInt(event.target.getAttribute('data-col'));
if (gameOver || board[row][col].isRevealed) return;
board[row][col].isFlagged = !board[row][col].isFlagged;
flagsPlaced += board[row][col].isFlagged ? 1 : -1;
flagsCountElement.textContent = flagsPlaced;
updateCellAppearance(event.target, board[row][col]);
}
// Reveal a cell and its neighbors if it has no adjacent mines
function revealCell(row, col) {
if (board[row][col].isRevealed || board[row][col].isFlagged) return;
board[row][col].isRevealed = true;
cellsRevealed++;
const cellElement = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
updateCellAppearance(cellElement, board[row][col]);
if (board[row][col].neighborMines === 0) {
const { rows, cols } = config[currentDifficulty];
for (let i = Math.max(0, row - 1); i <= Math.min(rows - 1, row + 1); i++) {
for (let j = Math.max(0, col - 1); j <= Math.min(cols - 1, col + 1); j++) {
if (i === row && j === col) continue;
revealCell(i, j);
}
}
}
}
// Reveal all mines on the board
function revealAllMines() {
const { rows, cols } = config[currentDifficulty];
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (board[i][j].isMine) {
board[i][j].isRevealed = true;
const cellElement = document.querySelector(`.cell[data-row="${i}"][data-col="${j}"]`);
updateCellAppearance(cellElement, board[i][j]);
}
}
}
}
// Set the game difficulty
function setDifficulty(difficulty) {
currentDifficulty = difficulty;
initGame();
}
// Event listeners for buttons
newGameButton.addEventListener('click', () => initGame());
easyButton.addEventListener('click', () => setDifficulty('easy'));
mediumButton.addEventListener('click', () => setDifficulty('medium'));
hardButton.addEventListener('click', () => setDifficulty('hard'));
// Initialize game on load
document.addEventListener('DOMContentLoaded', initGame);
// Prevent context menu on right-click
boardElement.addEventListener('contextmenu', (e) => e.preventDefault());
// Start the game
initGame();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment