| Server IP : 112.84.131.71 / Your IP : 43.159.78.200 Web Server : nginx/1.28.0 System : Linux 1763701629066 6.8.0-53-generic #55-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 17 15:37:52 UTC 2025 x86_64 User : www ( 1001) PHP Version : 8.2.28 Disable Function : passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /www/wwwroot/www.lengcat.cn/ |
Upload File : |
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>经典扫雷 - 自动翻开周边</title>
<style>
:root {
--bg-color: #f0f0f0;
--board-bg: #ccc;
--cell-bg: #ddd;
--cell-revealed: #fff;
--text-color: #333;
--btn-bg: #fff;
--btn-active-bg: #007acc;
}
.night-mode {
--bg-color: #1a1a1a;
--board-bg: #444;
--cell-bg: #555;
--cell-revealed: #333;
--text-color: #eee;
--btn-bg: #333;
--btn-active-bg: #005a9e;
}
* { box-sizing: border-box; margin: 0; padding: 0; user-select: none; -webkit-user-select: none; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: var(--bg-color);
color: var(--text-color);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
transition: background 0.3s, color 0.3s;
padding: 10px;
}
.container { text-align: center; width: 100%; max-width: 100%; }
h1 { margin-bottom: 10px; font-size: 1.5rem; }
.controls {
margin-bottom: 15px;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
align-items: center;
}
button {
padding: 6px 12px;
cursor: pointer;
background: var(--btn-bg);
border: 1px solid var(--board-bg);
border-radius: 4px;
color: var(--text-color);
font-size: 0.9rem;
transition: all 0.2s;
}
button:hover { opacity: 0.8; }
button.active { background: var(--btn-active-bg); color: white; border-color: var(--btn-active-bg); }
.custom-input { width: 50px; padding: 5px; border: 1px solid var(--board-bg); border-radius: 4px; background: var(--btn-bg); color: var(--text-color); text-align: center;}
.switch-label { display: flex; align-items: center; gap: 5px; cursor: pointer; font-size: 0.9rem; padding: 0 5px;}
.switch-label input { cursor: pointer; }
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 0 5px;
font-weight: bold;
font-size: 1.2rem;
font-family: 'Courier New', monospace;
}
.face-btn { font-size: 1.5rem; cursor: pointer; background: none; border: none; padding: 0; line-height: 1;}
.board {
display: inline-grid;
gap: 1px;
background: var(--board-bg);
padding: 5px;
border-radius: 5px;
margin: 0 auto;
touch-action: manipulation;
}
.cell {
width: 30px; height: 30px;
background: var(--cell-bg);
display: flex; justify-content: center; align-items: center;
font-weight: bold; font-size: 1rem;
cursor: pointer;
transition: background 0.1s;
}
.cell:active { background: var(--board-bg); }
.cell.revealed { background: var(--cell-revealed); cursor: default; }
.cell.flag::before { content: '🚩'; font-size: 1.1rem; }
.cell.question::before { content: '?'; color: #888; }
.cell.mine { background: #ffcccc; }
.cell.mine::before { content: '💣'; }
.cell.wrong-flag { background: #ffcccc; }
.cell.wrong-flag::before { content: '❌'; }
.c1 { color: blue; } .c2 { color: green; } .c3 { color: red; } .c4 { color: darkblue; }
.c5 { color: maroon; } .c6 { color: teal; } .c7 { color: black; } .c8 { color: gray; }
.night-mode .c7 { color: #ccc; }
@media (max-width: 600px) {
.cell { width: 26px; height: 26px; font-size: 0.85rem; }
.controls { gap: 5px; }
button { padding: 5px 8px; font-size: 0.8rem; }
}
.chord-hint {
opacity: 0.3;
transform: scale(0.9);
}
.c1 { color: blue; }
.c2 { color: green; }
.c3 { color: red; }
.c4 { color: darkblue; }
.c5 { color: darkred; }
</style>
</head>
<body>
<div class="container">
<h1>💣 扫雷</h1>
<div class="controls">
<button onclick="game.setDifficulty(9,9,10)" id="btn-b">基础</button>
<button onclick="game.setDifficulty(16,16,40)" id="btn-i">中级</button>
<button onclick="game.setDifficulty(16,30,99)" id="btn-e">专家</button>
<button onclick="game.setFullscreen()" id="btn-f">满屏</button>
<div style="display:flex; gap:4px; align-items:center;">
<input type="number" id="c-w" class="custom-input" value="20" min="5" max="50">
<span>×</span>
<input type="number" id="c-h" class="custom-input" value="20" min="5" max="50">
<input type="number" id="c-m" class="custom-input" value="60" min="1">
<button onclick="game.applyCustom()">GO</button>
</div>
<label class="switch-label">
<input type="checkbox" id="auto-flag-toggle" checked> 自动标雷
</label>
<label class="switch-label">
<input type="checkbox" id="night-toggle"> 🌙
</label>
</div>
<div class="status-bar">
<span id="mine-count">010</span>
<button class="face-btn" id="face" onclick="game.restart()">😀</button>
<span id="timer">000</span>
</div>
<div class="board" id="board"></div>
</div>
<script>
class Minesweeper {
constructor() {
this.rows = 9;
this.cols = 9;
this.mines = 10;
this.board = [];
this.state = [];
this.gameOver = false;
this.firstClick = true;
this.startTime = 0;
this.timerInterval = null;
this.flagCount = 0;
this.revealedCount = 0;
// 专业难度雷密度:99 / (16 * 30) ≈ 0.20625
this.expertDensity = 99 / (16 * 30);
this.boardEl = document.getElementById('board');
this.mineCountEl = document.getElementById('mine-count');
this.timerEl = document.getElementById('timer');
this.faceEl = document.getElementById('face');
this.autoFlagEl = document.getElementById('auto-flag-toggle');
document.getElementById('night-toggle').addEventListener('change', (e) => {
document.body.classList.toggle('night-mode', e.target.checked);
});
this.setDifficulty(9, 9, 10);
}
setDifficulty(r, c, m) {
this.rows = r; this.cols = c; this.mines = m;
this.updateActiveBtn();
this.restart();
}
applyCustom() {
const w = parseInt(document.getElementById('c-w').value) || 9;
const h = parseInt(document.getElementById('c-h').value) || 9;
const m = parseInt(document.getElementById('c-m').value) || 10;
if (m >= w * h) { alert("地雷太多啦!"); return; }
this.setDifficulty(h, w, m);
}
/**
* 满屏模式:根据屏幕尺寸自动计算行列数,保持专家难度雷密度
*/
setFullscreen() {
// 获取可用屏幕尺寸(减去控制栏、状态栏等高度)
const availableWidth = window.innerWidth - 20;
const availableHeight = window.innerHeight - 200;
// 最小格子尺寸
const minCellSize = 16;
const maxCellSize = 40;
// 估算一个合适的格子尺寸
let cellSize = Math.floor(Math.min(
availableWidth / 40, // 最多30列
availableHeight / 24, // 最多16行
maxCellSize
));
cellSize = Math.max(cellSize, minCellSize);
// 根据格子尺寸计算行列数
let cols = Math.floor(availableWidth / cellSize);
let rows = Math.floor(availableHeight / cellSize);
// 限制最小行列数
cols = Math.max(cols, 10);
rows = Math.max(rows, 10);
// 根据屏幕宽高比调整(如果宽屏,增加列数;如果竖屏,增加行数)
const aspectRatio = availableWidth / availableHeight;
if (aspectRatio > 1.5) {
// 宽屏,增加列数,减少行数
cols = Math.min(cols + 5, Math.floor(availableWidth / minCellSize));
rows = Math.max(Math.floor(cols / aspectRatio), 10);
} else if (aspectRatio < 0.7) {
// 竖屏,增加行数,减少列数
rows = Math.min(rows + 5, Math.floor(availableHeight / minCellSize));
cols = Math.max(Math.floor(rows * aspectRatio), 10);
}
// 计算地雷数(保持专家难度密度)
const totalCells = rows * cols;
const mines = Math.floor(totalCells * this.expertDensity);
this.setDifficulty(rows, cols, mines);
this.updateActiveBtn();
}
updateActiveBtn() {
document.querySelectorAll('.controls button').forEach(b => b.classList.remove('active'));
if (this.rows===9 && this.cols===9 && this.mines===10) document.getElementById('btn-b').classList.add('active');
else if (this.rows===16 && this.cols===16 && this.mines===40) document.getElementById('btn-i').classList.add('active');
else if (this.rows===16 && this.cols===30 && this.mines===99) document.getElementById('btn-e').classList.add('active');
// 满屏模式不标记active,因为行列数是动态的
}
restart() {
clearInterval(this.timerInterval);
this.board = Array(this.rows).fill().map(() => Array(this.cols).fill(0));
this.state = Array(this.rows).fill().map(() => Array(this.cols).fill(0));
this.gameOver = false;
this.firstClick = true;
this.flagCount = 0;
this.revealedCount = 0;
this.faceEl.textContent = '😀';
this.updateMineCounter();
this.timerEl.textContent = '000';
this.renderBoard();
}
initMines(safeR, safeC) {
let placed = 0;
const safeZone = new Set();
for(let dr=-1; dr<=1; dr++) {
for(let dc=-1; dc<=1; dc++) {
safeZone.add(`${safeR+dr},${safeC+dc}`);
}
}
while (placed < this.mines) {
const r = Math.floor(Math.random() * this.rows);
const c = Math.floor(Math.random() * this.cols);
if (this.board[r][c] === -1) continue;
if (safeZone.has(`${r},${c}`)) continue;
this.board[r][c] = -1;
placed++;
}
for (let r = 0; r < this.rows; r++) {
for (let c = 0; c < this.cols; c++) {
if (this.board[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 < this.rows && nc >= 0 && nc < this.cols && this.board[nr][nc] === -1) count++;
}
}
this.board[r][c] = count;
}
}
}
renderBoard() {
this.boardEl.innerHTML = '';
this.boardEl.style.gridTemplateColumns = `repeat(${this.cols}, 1fr)`;
// 动态调整格子尺寸以适应屏幕
const availableWidth = window.innerWidth - 40;
const availableHeight = window.innerHeight - 250;
const cellSize = Math.min(
Math.floor(availableWidth / this.cols),
Math.floor(availableHeight / this.rows),
40
);
const finalCellSize = Math.max(cellSize, 20);
for (let r = 0; r < this.rows; r++) {
for (let c = 0; c < this.cols; c++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.r = r;
cell.dataset.c = c;
cell.style.width = `${finalCellSize}px`;
cell.style.height = `${finalCellSize}px`;
cell.style.fontSize = `${Math.max(finalCellSize * 0.6, 12)}px`;
cell.onmousedown = (e) => {
if (this.gameOver) return;
if (e.button === 0) this.faceEl.textContent = '😮';
};
cell.onmouseup = (e) => {
if (this.gameOver) return;
this.faceEl.textContent = '😀';
};
cell.oncontextmenu = (e) => { e.preventDefault(); this.handleRightClick(r, c); };
cell.onclick = (e) => { if(e.button===0) this.handleClick(r, c); };
let pressTimer;
cell.ontouchstart = (e) => {
if (this.gameOver) return;
pressTimer = setTimeout(() => {
this.handleRightClick(r, c);
pressTimer = null;
}, 400);
};
cell.ontouchend = (e) => {
if (pressTimer) {
e.preventDefault();
this.handleClick(r, c);
}
};
cell.ontouchmove = () => clearTimeout(pressTimer);
this.boardEl.appendChild(cell);
}
}
}
getCellEl(r, c) {
return this.boardEl.querySelector(`[data-r="${r}"][data-c="${c}"]`);
}
updateCellUI(r, c) {
const el = this.getCellEl(r, c);
if (!el) return;
el.className = 'cell';
el.textContent = '';
const s = this.state[r][c];
const v = this.board[r][c];
if (s === 1) {
el.classList.add('revealed');
if (v === -1) {
el.classList.add('mine');
} else if (v > 0) {
el.textContent = v;
el.classList.add(`c${v}`);
}
} else if (s === 2) {
el.classList.add('flag');
} else if (s === 3) {
el.classList.add('question');
}
}
handleClick(r, c) {
if (this.gameOver) return;
const s = this.state[r][c];
// 如果点击的是已翻开的数字格子,尝试和弦操作(自动翻开周边)
if (s === 1 && this.board[r][c] > 0) {
this.chordOpen(r, c);
return;
}
// 如果点击的是隐藏格子或问号格子
if (s === 2) return; // 旗子不能点
if (this.firstClick) {
this.firstClick = false;
this.initMines(r, c);
this.startTimer();
}
if (this.board[r][c] === -1) {
this.lose(r, c);
return;
}
this.reveal(r, c);
if (this.autoFlagEl.checked) {
this.globalAutoFlag();
}
this.checkWin();
}
/**
* 和弦操作:当数字周围旗子数等于数字时,自动翻开周围未翻开的格子
*/
chordOpen(r, c) {
const val = this.board[r][c];
let flagCountAround = 0;
const hiddenCells = [];
// 统计周围旗子数和隐藏格子
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
const nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= this.rows || nc < 0 || nc >= this.cols) continue;
const s = this.state[nr][nc];
if (s === 2) {
flagCountAround++;
} else if (s === 0 || s === 3) {
hiddenCells.push({r: nr, c: nc});
}
}
}
// 只有当周围旗子数等于数字时,才触发自动翻开
if (flagCountAround === val && hiddenCells.length > 0) {
let hitMine = false;
let hitR, hitC;
hiddenCells.forEach(pos => {
if (this.board[pos.r][pos.c] === -1) {
hitMine = true;
hitR = pos.r;
hitC = pos.c;
} else {
this.reveal(pos.r, pos.c);
}
});
if (hitMine) {
this.lose(hitR, hitC);
return;
}
if (this.autoFlagEl.checked) {
this.globalAutoFlag();
}
this.checkWin();
}
}
handleRightClick(r, c) {
if (this.gameOver) return;
const s = this.state[r][c];
if (s === 1) return;
if (s === 0) this.state[r][c] = 2;
else if (s === 2) this.state[r][c] = 3;
else if (s === 3) this.state[r][c] = 0;
this.flagCount += (this.state[r][c] === 2 ? 1 : (s === 2 ? -1 : 0));
this.updateMineCounter();
this.updateCellUI(r, c);
}
reveal(r, c) {
if (r < 0 || r >= this.rows || c < 0 || c >= this.cols) return;
if (this.state[r][c] !== 0) return;
this.state[r][c] = 1;
this.revealedCount++;
this.updateCellUI(r, c);
if (this.board[r][c] === 0) {
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
this.reveal(r + dr, c + dc);
}
}
}
}
globalAutoFlag() {
let changed = true;
while (changed) {
changed = false;
const toFlag = [];
for (let r = 0; r < this.rows; r++) {
for (let c = 0; c < this.cols; c++) {
if (this.state[r][c] !== 1 || this.board[r][c] <= 0) continue;
const val = this.board[r][c];
let hiddenCount = 0;
let flagCountAround = 0;
const hiddenCells = [];
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
const nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= this.rows || nc < 0 || nc >= this.cols) continue;
const s = this.state[nr][nc];
if (s === 0 || s === 3) {
hiddenCount++;
hiddenCells.push({r: nr, c: nc});
} else if (s === 2) {
flagCountAround++;
}
}
}
const remainingMines = val - flagCountAround;
if (hiddenCount > 0 && hiddenCount === remainingMines) {
hiddenCells.forEach(pos => {
if (this.state[pos.r][pos.c] !== 2 && !toFlag.some(p => p.r === pos.r && p.c === pos.c)) {
toFlag.push(pos);
}
});
}
}
}
if (toFlag.length > 0) {
toFlag.forEach(pos => {
this.state[pos.r][pos.c] = 2;
this.flagCount++;
this.updateCellUI(pos.r, pos.c);
});
this.updateMineCounter();
changed = true;
}
}
}
lose(hitR, hitC) {
this.gameOver = true;
clearInterval(this.timerInterval);
this.faceEl.textContent = '😵';
for (let r = 0; r < this.rows; r++) {
for (let c = 0; c < this.cols; c++) {
if (this.board[r][c] === -1 && this.state[r][c] !== 2) {
this.state[r][c] = 1;
this.updateCellUI(r, c);
}
if (this.board[r][c] !== -1 && this.state[r][c] === 2) {
const el = this.getCellEl(r, c);
el.classList.remove('flag');
el.classList.add('wrong-flag');
}
}
}
const hitEl = this.getCellEl(hitR, hitC);
hitEl.style.background = '#ff0000';
}
checkWin() {
if (this.revealedCount === this.rows * this.cols - this.mines) {
this.gameOver = true;
clearInterval(this.timerInterval);
this.faceEl.textContent = '😎';
for(let r=0; r<this.rows; r++) {
for(let c=0; c<this.cols; c++) {
if(this.state[r][c] === 0 && this.board[r][c] === -1) {
this.state[r][c] = 2;
this.flagCount++;
this.updateCellUI(r, c);
}
}
}
this.updateMineCounter();
}
}
startTimer() {
this.startTime = Date.now();
this.timerInterval = setInterval(() => {
const t = Math.floor((Date.now() - this.startTime) / 1000);
this.timerEl.textContent = String(Math.min(t, 999)).padStart(3, '0');
}, 1000);
}
updateMineCounter() {
const left = this.mines - this.flagCount;
this.mineCountEl.textContent = String(Math.max(-99, Math.min(999, left))).padStart(3, '0');
}
}
const game = new Minesweeper();
</script>
</body>
</html>