Created
March 2, 2025 16:53
-
-
Save intellectronica/f16780e14602c94538e10acc196c99be to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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