Created
April 15, 2025 10:46
-
-
Save senko/171ca1e70531c4833e6c0a788b893a2b to your computer and use it in GitHub Desktop.
Minesweeper - single shot implementation by GPT 4.1
This file contains 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> | |
<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