Skip to content

Instantly share code, notes, and snippets.

@senko
Created April 15, 2025 10:46
Show Gist options
  • Save senko/171ca1e70531c4833e6c0a788b893a2b to your computer and use it in GitHub Desktop.
Save senko/171ca1e70531c4833e6c0a788b893a2b to your computer and use it in GitHub Desktop.
Minesweeper - single shot implementation by GPT 4.1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Minesweeper Clone</title>
<style>
body {
font-family: sans-serif;
background: #222;
color: #eee;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 40px;
}
#game {
display: inline-block;
border: 3px solid #888;
border-radius: 6px;
background: #444;
padding: 15px;
margin-top: 10px;
}
#board {
display: grid;
grid-template-columns: repeat(9, 32px);
grid-template-rows: repeat(9, 32px);
gap: 3px;
}
.cell {
width: 32px;
height: 32px;
font-size: 18px;
border: none;
border-radius: 2px;
background: #aaa;
color: #333;
cursor: pointer;
text-align: center;
outline: none;
user-select: none;
font-weight: bold;
transition: background 0.1s;
}
.cell:active {
background: #ccc;
}
.uncovered {
background: #eee;
cursor: default;
color: #222;
}
.mine {
background: #d9534f;
color: #fff;
}
.flag {
background: #f7ca18;
color: #222;
}
#message {
margin: 8px 0 0;
min-height: 26px;
font-size: 18px;
font-weight: bold;
text-align: center;
}
#reset {
margin-top: 12px;
padding: 4px 18px;
font-size: 18px;
border-radius: 4px;
border: none;
background: #337ab7;
color: #fff;
cursor: pointer;
}
#reset:hover { background: #23527c; }
.number1 { color: #297fb8; }
.number2 { color: #419441; }
.number3 { color: #e96633; }
.number4 { color: #59267c; }
.number5 { color: #d03a43; }
.number6 { color: #3dc5c9; }
.number7 { color: #666; }
.number8 { color: #222; }
</style>
</head>
<body>
<h1>Minesweeper</h1>
<div id="game">
<div id="board"></div>
<div id="message"></div>
<button id="reset">Restart</button>
</div>
<script>
const SIZE = 9, MINES = 10;
let board, revealed, flagged, mineSet, gameOver, cellsLeft;
const boardElem = document.getElementById('board');
const messageElem = document.getElementById('message');
const resetBtn = document.getElementById('reset');
function init() {
// State arrays
board = Array.from({length: SIZE}, ()=>Array(SIZE).fill(0));
revealed = Array.from({length: SIZE}, ()=>Array(SIZE).fill(false));
flagged = Array.from({length: SIZE}, ()=>Array(SIZE).fill(false));
mineSet = new Set();
gameOver = false;
cellsLeft = SIZE*SIZE-MINES;
messageElem.textContent = '';
boardElem.innerHTML = '';
// Generate mines
while (mineSet.size < MINES) {
let idx = Math.floor(Math.random() * SIZE * SIZE);
mineSet.add(idx);
}
// Place numbers
mineSet.forEach(idx => {
let r = Math.floor(idx/SIZE), c = idx%SIZE;
board[r][c] = 'M';
for (let dr=-1; dr<=1; dr++) for (let dc=-1; dc<=1; dc++) {
let nr=r+dr, nc=c+dc;
if (nr<0||nr>=SIZE||nc<0||nc>=SIZE||board[nr][nc]==='M') continue;
board[nr][nc]++;
}
});
// Render
for (let r=0; r<SIZE; r++) for(let c=0; c<SIZE; c++) {
const btn = document.createElement('button');
btn.className = 'cell';
btn.dataset.r = r;
btn.dataset.c = c;
btn.oncontextmenu = e => { e.preventDefault(); onFlag(r, c); };
btn.onclick = e => { if (e.button !== 2) onReveal(r, c); };
boardElem.appendChild(btn);
}
}
function onReveal(r, c) {
if (gameOver || revealed[r][c] || flagged[r][c]) return;
revealCell(r, c);
checkWin();
}
function revealCell(r, c) {
if(revealed[r][c]||flagged[r][c]) return;
revealed[r][c] = true;
const idx = r*SIZE + c;
const btn = boardElem.children[idx];
btn.classList.add('uncovered');
btn.disabled = true;
if (board[r][c] === 'M') {
btn.textContent = 'πŸ’£';
btn.classList.add('mine');
gameLost(r, c);
return;
}
cellsLeft--;
if (board[r][c] > 0) {
btn.textContent = board[r][c];
btn.classList.add('number'+board[r][c]);
} else {
btn.textContent = '';
// Flood fill
for (let dr=-1; dr<=1; dr++) for(let dc=-1; dc<=1; dc++) {
let nr=r+dr, nc=c+dc;
if (nr<0||nr>=SIZE||nc<0||nc>=SIZE||(dr==0&&dc==0)) continue;
revealCell(nr, nc);
}
}
}
function onFlag(r, c) {
if (gameOver || revealed[r][c]) return;
flagged[r][c] = !flagged[r][c];
const btn = boardElem.children[r*SIZE+c];
if (flagged[r][c]) {
btn.textContent = '🚩';
btn.classList.add('flag');
} else {
btn.textContent = '';
btn.classList.remove('flag');
}
checkWin();
}
function gameLost(rr, cc) {
messageElem.textContent = 'πŸ’₯ Game Over!';
gameOver = true;
// Reveal all mines
mineSet.forEach(idx => {
let r = Math.floor(idx/SIZE), c = idx%SIZE;
const btn = boardElem.children[idx];
btn.disabled = true;
if (!(r===rr && c===cc))
btn.textContent = 'πŸ’£', btn.classList.add('mine');
});
}
function checkWin() {
if (gameOver) return;
// Win: revealed all non-mine squares
if (cellsLeft === 0) {
messageElem.textContent = 'πŸ† You Win!';
gameOver = true;
// Reveal remaining mines
mineSet.forEach(idx => {
const btn = boardElem.children[idx];
btn.textContent = 'πŸ’£';
btn.classList.add('mine');
});
}
}
resetBtn.onclick = init;
init();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment