194 lines
5.5 KiB
HTML
194 lines
5.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Сапёр</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
background: #1a1a2e;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 100vh;
|
|
font-family: 'Segoe UI', sans-serif;
|
|
color: #eee;
|
|
}
|
|
h1 { color: #ffd700; margin-bottom: 10px; }
|
|
#info {
|
|
display: flex;
|
|
gap: 20px;
|
|
margin-bottom: 15px;
|
|
font-size: 1.1em;
|
|
}
|
|
.board {
|
|
display: grid;
|
|
grid-template-columns: repeat(10, 36px);
|
|
grid-template-rows: repeat(10, 36px);
|
|
gap: 2px;
|
|
background: #333;
|
|
padding: 4px;
|
|
border-radius: 8px;
|
|
}
|
|
.cell {
|
|
width: 36px;
|
|
height: 36px;
|
|
background: linear-gradient(145deg, #4a4a6a, #3a3a5a);
|
|
border: none;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.95em;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: 0.1s;
|
|
user-select: none;
|
|
}
|
|
.cell:hover:not(.revealed) { background: #5a5a7a; }
|
|
.cell.revealed { background: #2a2a4a; cursor: default; }
|
|
.cell.mine { background: #ff4444; }
|
|
.cell.flagged { background: #4a4a6a; }
|
|
.c1 { color: #4fc3f7; }
|
|
.c2 { color: #66bb6a; }
|
|
.c3 { color: #ef5350; }
|
|
.c4 { color: #7e57c2; }
|
|
.c5 { color: #ff7043; }
|
|
.c6 { color: #26c6da; }
|
|
.c7 { color: #ec407a; }
|
|
.c8 { color: #bdbdbd; }
|
|
#restart {
|
|
margin-top: 15px;
|
|
padding: 10px 30px;
|
|
font-size: 1em;
|
|
background: #ffd700;
|
|
color: #1a1a2e;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
}
|
|
#restart:hover { background: #ffcc00; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>💣 Сапёр</h1>
|
|
<div id="info">
|
|
<span>💣 Мины: <b id="mineCount">15</b></span>
|
|
<span>🚩 Флаги: <b id="flagCount">0</b></span>
|
|
</div>
|
|
<div class="board" id="board"></div>
|
|
<button id="restart">🔄 Заново</button>
|
|
|
|
<script>
|
|
const ROWS = 10, COLS = 10, MINES = 15;
|
|
let grid, revealed, flagged, gameOver, firstClick, flagCount;
|
|
|
|
function init() {
|
|
grid = Array(ROWS).fill(null).map(() => Array(COLS).fill(0));
|
|
revealed = Array(ROWS).fill(null).map(() => Array(COLS).fill(false));
|
|
flagged = Array(ROWS).fill(null).map(() => Array(COLS).fill(false));
|
|
gameOver = false;
|
|
firstClick = true;
|
|
flagCount = 0;
|
|
document.getElementById('mineCount').textContent = MINES;
|
|
document.getElementById('flagCount').textContent = 0;
|
|
render();
|
|
}
|
|
|
|
function placeMines(safeR, safeC) {
|
|
let placed = 0;
|
|
while (placed < MINES) {
|
|
const r = Math.floor(Math.random() * ROWS);
|
|
const c = Math.floor(Math.random() * COLS);
|
|
if (grid[r][c] === -1) continue;
|
|
if (Math.abs(r - safeR) <= 1 && Math.abs(c - safeC) <= 1) continue;
|
|
grid[r][c] = -1;
|
|
placed++;
|
|
}
|
|
for (let r = 0; r < ROWS; r++)
|
|
for (let c = 0; c < COLS; c++) {
|
|
if (grid[r][c] === -1) continue;
|
|
let count = 0;
|
|
for (let dr = -1; dr <= 1; dr++)
|
|
for (let dc = -1; dc <= 1; dc++) {
|
|
const nr = r + dr, nc = c + dc;
|
|
if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS && grid[nr][nc] === -1) count++;
|
|
}
|
|
grid[r][c] = count;
|
|
}
|
|
}
|
|
|
|
function render() {
|
|
const board = document.getElementById('board');
|
|
board.innerHTML = '';
|
|
for (let r = 0; r < ROWS; r++) {
|
|
for (let c = 0; c < COLS; c++) {
|
|
const cell = document.createElement('div');
|
|
cell.className = 'cell';
|
|
if (revealed[r][c]) {
|
|
cell.classList.add('revealed');
|
|
if (grid[r][c] === -1) {
|
|
cell.classList.add('mine');
|
|
cell.textContent = '💣';
|
|
} else if (grid[r][c] > 0) {
|
|
cell.textContent = grid[r][c];
|
|
cell.classList.add('c' + grid[r][c]);
|
|
}
|
|
} else if (flagged[r][c]) {
|
|
cell.classList.add('flagged');
|
|
cell.textContent = '🚩';
|
|
}
|
|
cell.addEventListener('click', () => reveal(r, c));
|
|
cell.addEventListener('contextmenu', e => { e.preventDefault(); toggleFlag(r, c); });
|
|
board.appendChild(cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
function reveal(r, c) {
|
|
if (gameOver || revealed[r][c] || flagged[r][c]) return;
|
|
if (firstClick) { placeMines(r, c); firstClick = false; }
|
|
revealed[r][c] = true;
|
|
if (grid[r][c] === -1) {
|
|
gameOver = true;
|
|
for (let i = 0; i < ROWS; i++) for (let j = 0; j < COLS; j++) if (grid[i][j] === -1) revealed[i][j] = true;
|
|
render();
|
|
setTimeout(() => alert('💥 Бум! Вы наступили на мину!'), 200);
|
|
return;
|
|
}
|
|
if (grid[r][c] === 0) {
|
|
for (let dr = -1; dr <= 1; dr++)
|
|
for (let dc = -1; dc <= 1; dc++) {
|
|
const nr = r + dr, nc = c + dc;
|
|
if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS) reveal(nr, nc);
|
|
}
|
|
}
|
|
checkWin();
|
|
render();
|
|
}
|
|
|
|
function toggleFlag(r, c) {
|
|
if (gameOver || revealed[r][c]) return;
|
|
flagged[r][c] = !flagged[r][c];
|
|
flagCount += flagged[r][c] ? 1 : -1;
|
|
document.getElementById('flagCount').textContent = flagCount;
|
|
render();
|
|
}
|
|
|
|
function checkWin() {
|
|
let count = 0;
|
|
for (let r = 0; r < ROWS; r++) for (let c = 0; c < COLS; c++) if (revealed[r][c]) count++;
|
|
if (count === ROWS * COLS - MINES) {
|
|
gameOver = true;
|
|
setTimeout(() => alert('🎉 Поздравляем! Вы нашли все мины!'), 200);
|
|
}
|
|
}
|
|
|
|
document.getElementById('restart').onclick = init;
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html> |