五子棋源码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>五子棋 - 人机对战 & 双人对战</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
padding: 20px;
overflow-x: hidden;
}
@keyframes gradientBG {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.container {
display: flex;
flex-direction: column;
align-items: center;
max-width: 1100px;
width: 100%;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(15px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);
color: white;
}
.header {
text-align: center;
margin-bottom: 25px;
width: 100%;
}
h1 {
font-size: 3.5rem;
color: #fff;
text-shadow: 0 0 20px rgba(255, 255, 255, 0.7);
margin-bottom: 5px;
position: relative;
display: inline-block;
font-weight: 700;
}
h1::after {
content: '';
position: absolute;
bottom: -10px;
left: 25%;
width: 50%;
height: 4px;
background: linear-gradient(90deg, transparent, #ffcc00, transparent);
border-radius: 2px;
}
.subtitle {
color: #e0e0e0;
font-size: 1.2rem;
max-width: 600px;
margin: 15px auto;
line-height: 1.6;
}
.game-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
width: 100%;
margin-top: 15px;
}
.board-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.status-panel {
display: flex;
justify-content: space-between;
width: 100%;
margin-bottom: 20px;
gap: 15px;
}
.status {
flex: 1;
background: rgba(255, 255, 255, 0.12);
padding: 15px 25px;
border-radius: 12px;
color: white;
font-weight: 500;
font-size: 1.2rem;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.status.active {
background: rgba(255, 217, 0, 0.2);
border: 2px solid #ffcc00;
}
.status i {
margin-right: 12px;
font-size: 1.4rem;
}
.black-status i {
color: #111;
}
.white-status i {
color: #ddd;
}
.board {
width: 520px;
height: 520px;
background-color: #dcb35c;
border: 3px solid #a67c52;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
border-radius: 4px;
position: relative;
overflow: hidden;
}
.board::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect x="0" y="0" width="100" height="100" fill="none" stroke="rgba(166, 124, 82, 0.3)" stroke-width="1"/></svg>');
}
.grid {
position: absolute;
top: 25px;
left: 25px;
width: calc(100% - 50px);
height: calc(100% - 50px);
display: grid;
grid-template-columns: repeat(15, 1fr);
grid-template-rows: repeat(15, 1fr);
}
.grid-line {
position: absolute;
background-color: rgba(0, 0, 0, 0.8);
}
.grid-line.horizontal {
width: 100%;
height: 1px;
left: 0;
}
.grid-line.vertical {
width: 1px;
height: 100%;
top: 0;
}
.board-point {
position: absolute;
width: 10px;
height: 10px;
background: #000;
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 1;
}
.piece {
position: absolute;
width: 32px;
height: 32px;
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 10;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
cursor: pointer;
}
.piece.black {
background: radial-gradient(circle at 30% 30%, #444, #000);
border: 1px solid #333;
}
.piece.white {
background: radial-gradient(circle at 30% 30%, #fff, #ccc);
border: 1px solid #aaa;
}
.piece.animate {
animation: drop 0.4s ease-out;
}
.piece.win {
box-shadow: 0 0 0 5px rgba(255, 217, 0, 0.8),
0 0 20px 8px rgba(255, 217, 0, 0.6);
animation: pulse 1.5s infinite;
}
.control-panel {
background: rgba(255, 255, 255, 0.1);
padding: 30px;
border-radius: 20px;
min-width: 350px;
color: white;
height: 520px;
display: flex;
flex-direction: column;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}
.panel-section {
margin-bottom: 25px;
}
.panel-title {
font-size: 1.6rem;
margin-bottom: 15px;
color: #ffcc00;
display: flex;
align-items: center;
}
.panel-title i {
margin-right: 10px;
}
.score-board {
display: flex;
justify-content: space-between;
gap: 15px;
}
.score {
flex: 1;
text-align: center;
padding: 15px;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
transition: all 0.3s;
}
.score.active {
background: rgba(255, 217, 0, 0.2);
transform: scale(1.03);
}
.score-title {
font-size: 1.2rem;
margin-bottom: 8px;
color: #ccc;
}
.score-value {
font-size: 3rem;
font-weight: bold;
color: #ffcc00;
text-shadow: 0 0 10px rgba(255, 204, 0, 0.5);
}
.controls {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 20px;
}
button {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
background: rgba(255, 255, 255, 0.15);
border: none;
padding: 14px;
border-radius: 12px;
color: white;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
button:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-2px);
}
button:active {
transform: translateY(1px);
}
button.primary {
background: linear-gradient(45deg, #ff8a00, #ff2070);
}
button.primary:hover {
background: linear-gradient(45deg, #ff9a2a, #ff3080);
}
.mode-selector {
display: flex;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 8px;
margin-bottom: 20px;
}
.mode-option {
flex: 1;
padding: 12px;
text-align: center;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s;
}
.mode-option.active {
background: rgba(255, 217, 0, 0.3);
box-shadow: 0 0 15px rgba(255, 204, 0, 0.3);
}
.difficulty {
display: flex;
flex-direction: column;
gap: 10px;
}
.difficulty label {
display: flex;
align-items: center;
gap: 15px;
padding: 14px 20px;
border-radius: 12px;
cursor: pointer;
transition: background 0.2s;
background: rgba(255, 255, 255, 0.1);
}
.difficulty label:hover {
background: rgba(255, 255, 255, 0.15);
}
.difficulty label.active {
background: rgba(255, 217, 0, 0.2);
}
.difficulty i {
font-size: 1.4rem;
}
.difficulty .fa-star {
color: #ffcc00;
}
.win-line {
position: absolute;
background-color: gold;
height: 6px;
border-radius: 3px;
z-index: 5;
transform-origin: 0 0;
}
@keyframes drop {
0% {
transform: translate(-50%, -50%) scale(1.8);
opacity: 0;
}
70% {
transform: translate(-50%, -50%) scale(0.95);
}
100% {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
}
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 4px rgba(255, 217, 0, 0.8);
}
50% {
box-shadow: 0 0 0 8px rgba(255, 217, 0, 0.4);
}
100% {
box-shadow: 0 0 0 4px rgba(255, 217, 0, 0.8);
}
}
.game-result {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s;
}
.result-content {
background: linear-gradient(135deg, #1a2a6c, #b21f1f);
padding: 50px;
border-radius: 20px;
text-align: center;
max-width: 600px;
width: 90%;
transform: scale(0.9);
transition: transform 0.5s;
box-shadow: 0 0 40px rgba(255, 204, 0, 0.6);
}
.result-title {
font-size: 3.5rem;
color: #ffcc00;
margin-bottom: 20px;
text-shadow: 0 0 20px rgba(255, 204, 0, 0.5);
}
.winner-name {
font-size: 2.2rem;
margin: 20px 0;
color: white;
}
.result-message {
font-size: 1.2rem;
margin-bottom: 30px;
line-height: 1.6;
}
/* 响应式设计 */
@media (max-width: 1100px) {
.board {
width: 450px;
height: 450px;
}
.control-panel {
min-width: 300px;
}
}
@media (max-width: 900px) {
.game-container {
flex-direction: column;
align-items: center;
}
.board {
width: 85vw;
height: 85vw;
max-width: 480px;
max-height: 480px;
}
.control-panel {
width: 100%;
max-width: 520px;
height: auto;
}
}
@media (max-width: 500px) {
h1 {
font-size: 2.5rem;
}
.board {
width: 95vw;
height: 95vw;
}
.status {
font-size: 1rem;
padding: 10px 15px;
}
.controls {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>五子棋</h1>
<p class="subtitle">策略与智慧的博弈游戏 - 在横、竖或对角线上连成五子即可获胜!</p>
</div>
<div class="game-container">
<div class="board-container">
<div class="status-panel">
<div class="status black-status active" id="blackStatus">
<i class="fas fa-circle"></i>
<span id="blackStatusText">黑棋</span>
</div>
<div class="status white-status" id="whiteStatus">
<i class="fas fa-circle"></i>
<span id="whiteStatusText">白棋</span>
</div>
</div>
<div class="board" id="board"></div>
</div>
<div class="control-panel">
<div class="score-board panel-section">
<div class="score black-score active">
<div class="score-title">黑棋得分</div>
<div class="score-value" id="playerScore">0</div>
</div>
<div class="score white-score">
<div class="score-title">白棋得分</div>
<div class="score-value" id="aiScore">0</div>
</div>
</div>
<div class="panel-section">
<div class="panel-title">
<i class="fas fa-cogs"></i>游戏设置
</div>
<div class="mode-selector">
<div class="mode-option active" data-mode="pvc">人机对战</div>
<div class="mode-option" data-mode="pvp">双人对战</div>
</div>
<div class="difficulty">
<label class="active">
<i class="fas fa-star"></i>
<span>初级难度(简单)</span>
<input type="radio" name="difficulty" value="easy" checked>
</label>
<label>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<span>中级难度(普通)</span>
<input type="radio" name="difficulty" value="medium">
</label>
<label>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<span>高级难度(困难)</span>
<input type="radio" name="difficulty" value="hard">
</label>
</div>
</div>
<div class="panel-section">
<div class="panel-title">
<i class="fas fa-gamepad"></i>游戏控制
</div>
<div class="controls">
<button class="primary" id="restartBtn">
<i class="fas fa-sync-alt"></i>开始新游戏
</button>
<button id="undoBtn">
<i class="fas fa-undo-alt"></i>悔棋
</button>
<button id="hintBtn">
<i class="fas fa-lightbulb"></i>提示
</button>
<button id="soundBtn">
<i class="fas fa-volume-up"></i>音效
</button>
</div>
</div>
</div>
</div>
</div>
<div class="game-result" id="gameResult">
<div class="result-content">
<h2 class="result-title">游戏结束</h2>
<div class="winner-name" id="winnerName">黑棋获胜!</div>
<p class="result-message" id="resultMessage">恭喜你赢得了比赛!你的棋艺精湛,策略运用得当。</p>
<button class="primary" id="playAgainBtn" style="margin-top: 20px; padding: 15px 30px;">
<i class="fas fa-redo"></i> 再玩一局
</button>
</div>
</div>
<script>
// 游戏主逻辑
document.addEventListener('DOMContentLoaded', () => {
const boardEl = document.getElementById('board');
const blackStatusText = document.getElementById('blackStatusText');
const whiteStatusText = document.getElementById('whiteStatusText');
const blackStatusEl = document.getElementById('blackStatus');
const whiteStatusEl = document.getElementById('whiteStatus');
const blackScoreEl = document.querySelector('.black-score');
const whiteScoreEl = document.querySelector('.white-score');
const restartBtn = document.getElementById('restartBtn');
const undoBtn = document.getElementById('undoBtn');
const hintBtn = document.getElementById('hintBtn');
const soundBtn = document.getElementById('soundBtn');
const playAgainBtn = document.getElementById('playAgainBtn');
const playerScoreEl = document.getElementById('playerScore');
const aiScoreEl = document.getElementById('aiScore');
const gameResultEl = document.getElementById('gameResult');
const winnerNameEl = document.getElementById('winnerName');
const resultMessageEl = document.getElementById('resultMessage');
const modeOptions = document.querySelectorAll('.mode-option');
const difficultyLabels = document.querySelectorAll('.difficulty label');
// 游戏状态变量
const BOARD_SIZE = 15;
let board = [];
let currentPlayer = 'black'; // 黑棋先行
let gameActive = true;
let gameMode = 'pvc'; // 'pvc' 人机对战, 'pvp' 双人对战
let blackScore = 0;
let whiteScore = 0;
let moveHistory = [];
let winLine = null;
let soundEnabled = true;
// 初始化棋盘
function initializeBoard() {
board = [];
boardEl.innerHTML = '';
moveHistory = [];
// 创建网格线
createGrid();
// 创建棋盘中心点
createBoardPoints();
// 初始化空棋盘
for (let i = 0; i < BOARD_SIZE; i++) {
board[i] = [];
for (let j = 0; j < BOARD_SIZE; j++) {
board[i][j] = null;
}
}
gameActive = true;
currentPlayer = 'black';
updateStatus();
// 更新分数显示
playerScoreEl.textContent = blackScore;
aiScoreEl.textContent = whiteScore;
// 移除胜利连线
if (winLine) {
winLine.remove();
winLine = null;
}
// 重置分数高亮
blackScoreEl.classList.add('active');
whiteScoreEl.classList.remove('active');
}
// 创建棋盘网格线
function createGrid() {
const gridSize = BOARD_SIZE - 1;
const cellSize = (boardEl.clientWidth - 50) / gridSize;
// 横线
for (let i = 0; i < BOARD_SIZE; i++) {
const hLine = document.createElement('div');
hLine.classList.add('grid-line', 'horizontal');
hLine.style.top = `${25 + i * cellSize}px`;
boardEl.appendChild(hLine);
}
// 竖线
for (let i = 0; i < BOARD_SIZE; i++) {
const vLine = document.createElement('div');
vLine.classList.add('grid-line', 'vertical');
vLine.style.left = `${25 + i * cellSize}px`;
boardEl.appendChild(vLine);
}
}
// 创建棋盘标记点
function createBoardPoints() {
const points = [
[3, 3], [3, 11], [7, 7], [11, 3], [11, 11]
];
const cellSize = (boardEl.clientWidth - 50) / (BOARD_SIZE - 1);
points.forEach(point => {
const [x, y] = point;
const pointEl = document.createElement('div');
pointEl.classList.add('board-point');
pointEl.style.left = `${25 + x * cellSize}px`;
pointEl.style.top = `${25 + y * cellSize}px`;
boardEl.appendChild(pointEl);
});
}
// 更新游戏状态显示
function updateStatus() {
if (!gameActive) return;
blackStatusText.textContent = gameMode === 'pvc'
? (currentPlayer === 'black' ? '您落子 (黑棋)' : '电脑思考中...')
: (currentPlayer === 'black' ? '黑方回合' : '白方回合');
whiteStatusText.textContent = gameMode === 'pvc'
? '电脑 (白棋)'
: '白方';
// 更新当前玩家高亮
if (currentPlayer === 'black') {
blackStatusEl.classList.add('active');
whiteStatusEl.classList.remove('active');
blackScoreEl.classList.add('active');
whiteScoreEl.classList.remove('active');
} else {
whiteStatusEl.classList.add('active');
blackStatusEl.classList.remove('active');
whiteScoreEl.classList.add('active');
blackScoreEl.classList.remove('active');
}
}
// 处理玩家落子
function handlePlayerMove(e) {
if (!gameActive || (gameMode === 'pvc' && currentPlayer !== 'black') ||
(gameMode === 'pvp' && !gameActive)) return;
const rect = boardEl.getBoundingClientRect();
const cellSize = (rect.width - 50) / (BOARD_SIZE - 1);
// 计算点击位置
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 排除边缘区域
if (x < 15 || x > rect.width - 15 || y < 15 || y > rect.height - 15) return;
// 转换为网格坐标
const col = Math.round((x - 25) / cellSize);
const row = Math.round((y - 25) / cellSize);
// 检查位置是否有效
if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && !board[row][col]) {
placePiece(row, col, currentPlayer);
playSound('place');
// 检查是否获胜
if (checkWin(row, col, currentPlayer)) {
endGame(currentPlayer);
return;
}
// 切换到下一位玩家
currentPlayer = currentPlayer === 'black' ? 'white' : 'black';
updateStatus();
// 如果是人机模式且轮到AI,则执行AI落子
if (gameMode === 'pvc' && currentPlayer === 'white') {
setTimeout(makeAIMove, 600);
}
}
}
// 在指定位置放置棋子
function placePiece(row, col, player) {
// 存储历史记录用于悔棋
moveHistory.push({row, col, player});
// 更新棋盘数据
board[row][col] = player;
// 创建棋子元素
const piece = document.createElement('div');
piece.classList.add('piece', player, 'animate');
// 计算像素位置
const cellSize = (boardEl.clientWidth - 50) / (BOARD_SIZE - 1);
const left = 25 + col * cellSize;
const top = 25 + row * cellSize;
piece.style.left = `${left}px`;
piece.style.top = `${top}px`;
boardEl.appendChild(piece);
// 保存棋子引用
board[row][col] = {element: piece, player};
}
// AI落子逻辑
function makeAIMove() {
if (!gameActive || currentPlayer !== 'white') return;
let bestMove = findBestMove();
if (bestMove) {
const {row, col} = bestMove;
setTimeout(() => {
placePiece(row, col, 'white');
playSound('place');
// 检查AI是否获胜
if (checkWin(row, col, 'white')) {
endGame('white');
return;
}
// 切换回玩家
currentPlayer = 'black';
updateStatus();
}, 300);
}
}
// 查找最佳移动
function findBestMove() {
const difficulty = document.querySelector('input[name="difficulty"]:checked').value;
// 简单AI:随机落子
if (difficulty === 'easy') {
return findRandomMove();
}
// 中等AI:基本策略(防守+进攻)
if (difficulty === 'medium') {
return findMediumMove();
}
// 困难AI:高级策略(两步思考)
return findHardMove();
}
// 随机移动(简单AI)
function findRandomMove() {
const possibleMoves = [];
// 收集所有空位
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (!board[row][col]) {
possibleMoves.push({row, col});
}
}
}
// 随机选择一个位置
if (possibleMoves.length > 0) {
return possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
}
return null;
}
// 中等AI策略
function findMediumMove() {
// 1. 如果AI可以获胜,则选择这个位置
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (!board[row][col]) {
// 模拟AI落子
board[row][col] = 'white';
if (checkWin(row, col, 'white')) {
board[row][col] = null; // 重置
return {row, col};
}
board[row][col] = null; // 重置
}
}
}
// 2. 阻止玩家获胜
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (!board[row][col]) {
// 模拟玩家落子
board[row][col] = 'black';
if (checkWin(row, col, 'black')) {
board[row][col] = null; // 重置
return {row, col};
}
board[row][col] = null; // 重置
}
}
}
// 3. 如果中心区域有空位,优先选择
const centerArea = [];
const centerMin = Math.floor(BOARD_SIZE * 0.4);
const centerMax = Math.floor(BOARD_SIZE * 0.6);
for (let row = centerMin; row <= centerMax; row++) {
for (let col = centerMin; col <= centerMax; col++) {
if (!board[row][col]) {
centerArea.push({row, col});
}
}
}
if (centerArea.length > 0) {
return centerArea[Math.floor(Math.random() * centerArea.length)];
}
// 4. 随机落子
return findRandomMove();
}
// 困难AI策略
function findHardMove() {
// 1. 获胜机会
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (!board[row][col]) {
board[row][col] = 'white';
if (checkWin(row, col, 'white')) {
board[row][col] = null;
return {row, col};
}
board[row][col] = null;
}
}
}
// 2. 阻止玩家获胜
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (!board[row][col]) {
board[row][col] = 'black';
if (checkWin(row, col, 'black')) {
board[row][col] = null;
return {row, col};
}
board[row][col] = null;
}
}
}
// 3. 寻找双三、活三等机会
let bestScore = -Infinity;
let bestMove = null;
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (!board[row][col]) {
// 计算此位置的价值
let score = evaluatePosition(row, col);
if (score > bestScore) {
bestScore = score;
bestMove = {row, col};
}
}
}
}
return bestMove;
}
// 评估位置价值(困难AI使用)
function evaluatePosition(row, col) {
let score = 0;
// 位置权重 (中心位置价值更高)
const center = BOARD_SIZE / 2;
const distance = Math.sqrt(Math.pow(row - center, 2) + Math.pow(col - center, 2));
score += 10 / (distance + 1);
// 方向数组: 水平、垂直、对角线
const directions = [
[0, 1], [1, 0], [1, 1], [1, -1]
];
// 计算每个方向上的得分
for (const [dx, dy] of directions) {
score += getDirectionScore(row, col, dx, dy);
}
return score;
}
function getDirectionScore(row, col, dx, dy) {
let aiScore = 0;
let playerScore = 0;
// 分析一个方向上的棋子情况
for (let i = -4; i <= 4; i++) {
if (i === 0) continue; // 跳过当前位置
const r = row + dx * i;
const c = col + dy * i;
if (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE) {
if (board[r][c] && board[r][c].player === 'white') {
aiScore++;
} else if (board[r][c] && board[r][c].player === 'black') {
playerScore++;
}
}
}
// 计算得分
let score = 0;
// AI的潜在连子
if (aiScore === 4) score += 1000;
if (aiScore === 3) score += 100;
if (aiScore === 2) score += 10;
if (aiScore === 1) score += 1;
// 阻止玩家的连子
if (playerScore === 4) score += 500;
if (playerScore === 3) score += 50;
if (playerScore === 2) score += 5;
return score;
}
// 检查是否获胜
function checkWin(row, col, player) {
const directions = [
[[0, 1], [0, -1]], // 水平
[[1, 0], [-1, 0]], // 垂直
[[1, 1], [-1, -1]], // 对角线
[[1, -1], [-1, 1]] // 反对角线
];
for (const [positive, negative] of directions) {
let count = 1; // 当前位置已经有一个棋子
// 正方向
for (let i = 1; i < 5; i++) {
const newRow = row + positive[0] * i;
const newCol = col + positive[1] * i;
if (newRow < 0 || newRow >= BOARD_SIZE ||
newCol < 0 || newCol >= BOARD_SIZE ||
!board[newRow][newCol] || board[newRow][newCol].player !== player) {
break;
}
count++;
}
// 反方向
for (let i = 1; i < 5; i++) {
const newRow = row + negative[0] * i;
const newCol = col + negative[1] * i;
if (newRow < 0 || newRow >= BOARD_SIZE ||
newCol < 0 || newCol >= BOARD_SIZE ||
!board[newRow][newCol] || board[newRow][newCol].player !== player) {
break;
}
count++;
}
if (count >= 5) {
createWinLine(row, col, positive, negative);
return true;
}
}
return false;
}
// 创建胜利连线
function createWinLine(row, col, positive, negative) {
const cellSize = (boardEl.clientWidth - 50) / (BOARD_SIZE - 1);
// 计算起点
let startRow = row;
let startCol = col;
// 找到连线起点
while (true) {
const prevRow = startRow + negative[0];
const prevCol = startCol + negative[1];
if (prevRow < 0 || prevRow >= BOARD_SIZE ||
prevCol < 0 || prevCol >= BOARD_SIZE ||
!board[prevRow][prevCol] || board[prevRow][prevCol].player !== board[row][col].player) {
break;
}
startRow = prevRow;
startCol = prevCol;
}
// 计算终点
let endRow = row;
let endCol = col;
while (true) {
const nextRow = endRow + positive[0];
const nextCol = endCol + positive[1];
if (nextRow < 0 || nextRow >= BOARD_SIZE ||
nextCol < 0 || nextCol >= BOARD_SIZE ||
!board[nextRow][nextCol] || board[nextRow][nextCol].player !== board[row][col].player) {
break;
}
endRow = nextRow;
endCol = nextCol;
}
// 创建连线元素
const startX = 25 + startCol * cellSize;
const startY = 25 + startRow * cellSize;
const endX = 25 + endCol * cellSize;
const endY = 25 + endRow * cellSize;
// 计算线段的长度和角度
const dx = endX - startX;
const dy = endY - startY;
const length = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
winLine = document.createElement('div');
winLine.classList.add('win-line');
winLine.style.width = `${length}px`;
winLine.style.left = `${startX}px`;
winLine.style.top = `${startY}px`;
winLine.style.transform = `rotate(${angle}deg)`;
boardEl.appendChild(winLine);
// 标记胜利棋子
for (let r = startRow; r <= endRow; r++) {
for (let c = startCol; c <= endCol; c++) {
if (board[r][c] && board[r][c].element) {
board[r][c].element.classList.add('win');
}
}
}
}
// 结束游戏
function endGame(winner) {
gameActive = false;
// 更新得分
if (winner === 'black') {
blackScore++;
winnerNameEl.textContent = gameMode === 'pvc' ? '您获胜了!' : '黑方获胜!';
resultMessageEl.textContent = gameMode === 'pvc'
? '恭喜你赢得了比赛!你的棋艺精湛,策略运用得当。'
: '恭喜黑方获胜!精彩的胜利,精湛的棋艺!';
} else {
whiteScore++;
winnerNameEl.textContent = gameMode === 'pvc' ? '电脑获胜!' : '白方获胜!';
resultMessageEl.textContent = gameMode === 'pvc'
? '电脑获得了胜利,继续加油,下次一定能赢!'
: '恭喜白方获胜!出色的策略,完美的胜利!';
}
// 显示结果弹窗
gameResultEl.style.opacity = '1';
gameResultEl.style.pointerEvents = 'all';
setTimeout(() => {
gameResultEl.querySelector('.result-content').style.transform = 'scale(1)';
}, 10);
}
// 提示功能
function showHint() {
if (!gameActive || currentPlayer !== 'black' || gameMode !== 'pvc') return;
const possibleMoves = [];
// 收集所有空位
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (!board[row][col]) {
possibleMoves.push({row, col});
}
}
}
// 随机选择一个提示位置(实际应用中应该使用算法找到最佳位置)
if (possibleMoves.length > 0) {
const {row, col} = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
// 添加提示标记
const hintElement = document.createElement('div');
hintElement.style.position = 'absolute';
hintElement.style.left = `${25 + col * ((boardEl.clientWidth - 50) / (BOARD_SIZE - 1))}px`;
hintElement.style.top = `${25 + row * ((boardEl.clientWidth - 50) / (BOARD_SIZE - 1))}px`;
hintElement.style.width = '15px';
hintElement.style.height = '15px';
hintElement.style.borderRadius = '50%';
hintElement.style.backgroundColor = '#ffcc00';
hintElement.style.transform = 'translate(-50%, -50%)';
hintElement.style.zIndex = '5';
hintElement.style.animation = 'pulse 1.5s infinite';
boardEl.appendChild(hintElement);
// 3秒后移除提示
setTimeout(() => {
hintElement.remove();
}, 3000);
}
}
// 播放音效
function playSound(type) {
if (!soundEnabled) return;
// 在实际应用中这里应该加载音频文件
// 这是演示版本,所以只是控制台输出
console.log(`播放音效: ${type}`);
}
// 初始化游戏
initializeBoard();
// 事件监听
boardEl.addEventListener('click', handlePlayerMove);
restartBtn.addEventListener('click', () => {
initializeBoard();
playSound('restart');
});
undoBtn.addEventListener('click', () => {
if (moveHistory.length < 1 || !gameActive) return;
// 移除最后一步
const lastMove = moveHistory.pop();
if (lastMove && board[lastMove.row][lastMove.col]?.element) {
board[lastMove.row][lastMove.col].element.remove();
}
board[lastMove.row][lastMove.col] = null;
// 如果上一步是AI/对手下的,再移除玩家上一步
if (moveHistory.length > 0 && moveHistory[moveHistory.length - 1].player === currentPlayer) {
const previousMove = moveHistory.pop();
if (previousMove && board[previousMove.row][previousMove.col]?.element) {
board[previousMove.row][previousMove.col].element.remove();
}
board[previousMove.row][previousMove.col] = null;
}
// 重置为当前玩家回合
currentPlayer = currentPlayer === 'black' ? 'white' : 'black';
gameActive = true;
updateStatus();
// 移除胜利连线
if (winLine) {
winLine.remove();
winLine = null;
}
playSound('undo');
});
hintBtn.addEventListener('click', showHint);
soundBtn.addEventListener('click', () => {
soundEnabled = !soundEnabled;
soundBtn.innerHTML = soundEnabled
? '<i class="fas fa-volume-up"></i> 音效'
: '<i class="fas fa-volume-mute"></i> 音效';
});
playAgainBtn.addEventListener('click', () => {
gameResultEl.style.opacity = '0';
gameResultEl.style.pointerEvents = 'none';
gameResultEl.querySelector('.result-content').style.transform = 'scale(0.9)';
initializeBoard();
});
// 游戏模式选择
modeOptions.forEach(option => {
option.addEventListener('click', () => {
modeOptions.forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
gameMode = option.dataset.mode;
initializeBoard();
playSound('mode');
});
});
// 难度选择
difficultyLabels.forEach(label => {
const input = label.querySelector('input');
label.addEventListener('click', () => {
difficultyLabels.forEach(l => l.classList.remove('active'));
label.classList.add('active');
playSound('difficulty');
});
});
});
</script>
</body>
</html>
扫雷源码:
<!DOCTYPE html>
<html>
<head>
<title>扫雷小游戏</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
margin: 0;
}
</style>
<style>
:root {
--grid-width: 350;
--tile-width: 35;
--main-color: #8B6AF5;
}
body {
padding: 0;
margin: 0;
background: #f9f8fe;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
font-family: "Roboto Mono", monospace;
}
*,
*:before,
*:after {
box-sizing: border-box;
}
.container {
width: calc(var(--grid-width) * 1px);
align-content: center;
text-align: center;
box-shadow: 5px 5px 20px 0 rgba(100, 74, 74, 0.1);
position: relative;
z-index: 2;
}
.header {
display: flex;
position: relative;
align-items: flex-start;
background-color: var(--main-color);
color: white;
justify-content: space-between;
padding: 1rem;
}
.grid {
height: calc(var(--grid-width) * 1px);
width: 100%;
display: flex;
flex-wrap: wrap;
}
.tile {
height: calc(var(--tile-width) * 1px);
width: calc(var(--tile-width) * 1px);
cursor: pointer;
border: 2px solid;
border-color: white rgb(221.1428571429, 215.5, 255) rgb(221.1428571429, 215.5, 255) white;
box-sizing: border-box;
background-color: #f3f1ff;
font-weight: 700;
font-size: 25px;
display: flex;
align-items: center;
justify-content: center;
}
.tile svg {
width: 80%;
}
.checked {
border: 1px solid;
background-color: rgb(234.2571428571, 230.8, 255);
border-color: rgb(221.1428571429, 215.5, 255);
}
#refresh {
cursor: pointer;
width: 30px;
align-self: flex-end;
}
.dropdown {
color: white;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 3px;
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 1rem;
text-align: center;
}
.dropdown .title {
width: 100%;
padding: 0.5rem 1rem;
cursor: pointer;
}
.dropdown .menu {
background: var(--main-color);
position: absolute;
overflow: hidden;
cursor: pointer;
display: none;
width: 5rem;
text-align: left;
line-height: 1.4rem;
}
.dropdown .menu.show {
display: block;
}
.dropdown .menu .option {
padding: 0.5rem;
}
.dropdown .menu .option:hover {
background: rgba(255, 255, 255, 0.2);
}
.has-bomb {
transition: background 0.25s ease-in;
}
#flag-countdown, #timer {
display: flex;
font-size: 35px;
}
#flag-countdown span, #timer span {
margin-left: 0.5rem;
}
#modal {
position: fixed;
background-color: rgba(57, 57, 91, 0.2);
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999;
visibility: hidden;
opacity: 0;
pointer-events: none;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
}
#modal.show {
visibility: visible;
opacity: 1;
pointer-events: auto;
}
#modal .modal-close {
text-align: right;
}
#modal h2 {
color: var(--main-color);
}
#result-box {
background-color: #f9f8fe;
box-shadow: 5px 5px 20px 0 rgba(100, 74, 74, 0.1);
border-radius: 4px;
min-width: 400px;
text-align: center;
}
#result-top {
margin: 2rem;
}
#result-message {
color: var(--dark-color);
font-size: 40px;
}
.result-time {
display: none;
}
.show {
display: block;
}
#new-game {
padding: 0.5rem;
background-color: var(--main-color);
color: #fff;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
margin: 1rem;
height: 60px;
border-radius: 4px;
font-family: sans-serif;
}
#new-game * {
display: inline-block;
}
#new-game h2 {
line-height: 30px;
margin: 0 0 0 1rem;
color: white;
}
#background {
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
}
#background svg {
width: 50px;
height: 40px;
opacity: 0.3;
position: absolute;
}
svg#bomb {
width: 200px;
}
svg#bomb .sad-face, svg#bomb .happy-face {
display: none;
}
svg#bomb .sad-face.show, svg#bomb .happy-face.show {
display: block;
}
svg#bomb .bomb-fill {
fill: var(--main-color);
}
svg#bomb .bomb-stroke {
stroke: var(--dark-color);
}
svg#bomb .bomb-fill-dark {
fill: var(--dark-color);
}
svg.hide {
display: none;
}
.shake {
animation: shake 0.75s cubic-bezier(0.38, 0.06, 0.22, 0.95) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
}
@keyframes shake {
10%, 90% {
transform: translate3d(-1px, 0, 0);
}
20%, 80% {
transform: translate3d(2px, 0, 0);
}
30%, 50%, 70% {
transform: translate3d(-3px, 0, 0);
}
40%, 60% {
transform: translate3d(2px, 0, 0);
}
}
</style>
</head>
<body>
<svg id="main" class="hide">
<symbol id="bomb-svg" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 786.7 687.8" width="50px">
<path d="M427.4,144.2l40.7-41.8a23.1,23.1,0,0,1,32.5,0l72.8,72.8a23.1,23.1,0,0,1,0,32.5l-37.4,37.4" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<circle cx="291.1" cy="385.2" r="282.6" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<path d="M540.2,141.2l15.4-20.5c12.6-16.8,30.4-17.7,43.6-2.4l0.3,0.3c11.8,13.7,31.3,22.6,48.3,11.4,6.1-4,20.7-20.5,20.7-20.5" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<line x1="701.7" y1="63.3" x2="742" y2="23.1" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<line x1="713.2" y1="107.4" x2="766.2" y2="128.2" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<line x1="654.5" y1="60.2" x2="630.8" y2="8.5" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<path d="M82,396.8c0-118.4,95.9-214.3,214.3-214.3" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<g id="Happy_face" data-name="Happy face">
<path d="M170.4,432.1a34.6,34.6,0,0,1,69.2,0" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<path d="M342.5,432.1a34.6,34.6,0,0,1,69.2,0" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
<path d="M367,481.7c0,33.7-33.4,64.1-74.6,64.1s-74.6-30.3-74.6-64.1" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
</g>
</symbol>
</svg>
<div id="background"></div>
<div id="modal">
<div id="result-box">
<div id="result-top">
<svg id="bomb" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 774.7 676.2">
<path d="M427.4,144.2l40.7-41.8a23.1,23.1,0,0,1,32.5,0l72.8,72.8a23.1,23.1,0,0,1,0,32.5l-37.4,37.4" transform="translate(0 -11.6)" fill="#8b6af5" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke bomb-fill"/>
<circle cx="291.1" cy="385.2" r="282.6" fill="#8b6af5" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke bomb-fill"/>
<path d="M540.2,141.2l15.4-20.5c12.6-16.8,30.4-17.7,43.6-2.4l0.3,0.3c11.8,13.7,31.3,22.6,48.3,11.4,6.1-4,20.7-20.5,20.7-20.5" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
<line x1="701.7" y1="63.3" x2="742" y2="23.1" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
<line x1="713.2" y1="107.4" x2="766.2" y2="128.2" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
<line x1="654.5" y1="60.2" x2="630.8" y2="8.5" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
<path d="M82,396.8c0-118.4,95.9-214.3,214.3-214.3" transform="translate(0 -11.6)" fill="none" stroke="#fff" stroke-linecap="round" stroke-miterlimit="10" stroke-opacity="0.45" stroke-width="17"/>
<g id="happy-face" class="happy-face">
<path d="M170.4,432.1a34.6,34.6,0,0,1,69.2,0" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
<path d="M342.5,432.1a34.6,34.6,0,0,1,69.2,0" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
<path d="M367,481.7c0,33.7-33.4,64.1-74.6,64.1s-74.6-30.3-74.6-64.1" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
</g>
<g id="sad-face" class="sad-face">
<g>
<circle cx="377.1" cy="406" r="17.7" fill="#39395b" class="bomb-fill-dark"/>
<path d="M250,517.1c0-19.2,17.6-42.5,41.1-42.5s43.8,23.3,43.8,42.5" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
<circle cx="205" cy="406" r="17.7" fill="#39395b" class="bomb-fill-dark"/>
</g>
</g>
</svg>
<h1 id="result-message"></h1>
<h2 class="result-time">Your time: <span class="time-display"></span> seconds</h2>
</div>
<div id="new-game">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 512 512" enable-background="new 0 0 512 512" fill="#fff" width="30">
<g>
<path d="M480.6,235.6c-11.3,0-20.4,9.1-20.4,20.4c0,112.6-91.6,204.2-204.2,204.2c-112.6,0-204.2-91.6-204.2-204.2 S143.4,51.8,256,51.8c61.5,0,118.5,27.1,157.1,73.7h-70.5c-11.3,0-20.4,9.1-20.4,20.4s9.1,20.4,20.4,20.4h114.6 c11.3,0,20.4-9.1,20.4-20.4V31.4c0-11.3-9.1-20.4-20.4-20.4s-20.4,9.1-20.4,20.4v59C390.7,40.1,325.8,11,256,11 C120.9,11,11,120.9,11,256c0,135.1,109.9,245,245,245s245-109.9,245-245C501,244.7,491.9,235.6,480.6,235.6z"/>
</g>
</svg>
<h2>重新开始</h2>
</div>
</div>
</div>
<div class="container">
<div class="header">
<div class='dropdown'>
<div class='title'>初级</div>
<div class='menu'>
<div class='option' id='option1'>初级</div>
<div class='option' id='option2'>中级</div>
<div class='option' id='option3'>高级</div>
</div>
</div>
<div id='flag-countdown'><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 287.987 287.987" fill="#fff" width="30" style="enable-background:new 0 0 287.987 287.987;" xml:space="preserve"><g><path d="M228.702,141.029c-3.114-3.754-3.114-9.193,0-12.946l33.58-40.474c2.509-3.024,3.044-7.226,1.374-10.783 c-1.671-3.557-5.246-5.828-9.176-5.828h-57.647v60.98c0,16.618-13.52,30.138-30.138,30.138h-47.093v25.86 c0,5.599,4.539,10.138,10.138,10.138h124.74c3.93,0,7.505-2.271,9.176-5.828c1.671-3.557,1.135-7.759-1.374-10.783L228.702,141.029 z"/><path d="M176.832,131.978V25.138c0-5.599-4.539-10.138-10.138-10.138H53.37c0-8.284-6.716-15-15-15s-15,6.716-15,15 c0,7.827,0,253.91,0,257.987c0,8.284,6.716,15,15,15s15-6.716,15-15c0-6.943,0-126.106,0-130.871h113.324 C172.293,142.116,176.832,137.577,176.832,131.978z"/></g></svg><span id='flags-left'></span></div>
<div id="timer"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 559.98 559.98" fill="#fff" width="30px" style="enable-background:new 0 0 559.98 559.98;" xml:space="preserve"><g><path d="M279.99,0C125.601,0,0,125.601,0,279.99c0,154.39,125.601,279.99,279.99,279.99c154.39,0,279.99-125.601,279.99-279.99 C559.98,125.601,434.38,0,279.99,0z M279.99,498.78c-120.644,0-218.79-98.146-218.79-218.79 c0-120.638,98.146-218.79,218.79-218.79s218.79,98.152,218.79,218.79C498.78,400.634,400.634,498.78,279.99,498.78z"/><path d="M304.226,280.326V162.976c0-13.103-10.618-23.721-23.716-23.721c-13.102,0-23.721,10.618-23.721,23.721v124.928 c0,0.373,0.092,0.723,0.11,1.096c-0.312,6.45,1.91,12.999,6.836,17.926l88.343,88.336c9.266,9.266,24.284,9.266,33.543,0 c9.26-9.266,9.266-24.284,0-33.544L304.226,280.326z"/></g></svg><span class="counter">000</span></div>
</div>
<div class="grid"></div>
</div>
<script type="text/javascript">// watch Ania Kubow's tutorial here: [url]https://www.youtube.com/watch?v=rxdGAKRndz8[/url]
document.addEventListener("DOMContentLoaded", () => {
const grid = document.querySelector(".grid");
const container = document.querySelector(".container");
const flagsLeft = document.querySelector("#flags-left");
const result = document.querySelector("#result-message");
const modal = document.querySelector("#modal");
const background = document.querySelector("#background");
const bombSadFace = document.querySelector("#sad-face");
const bombHappyFace = document.querySelector("#happy-face");
const timer = document.querySelector(".counter");
const finalTimeDisplay = document.querySelector(".time-display");
const resultTime = document.querySelector(".result-time");
const levels = [
{
name: "初级",
width: 10,
bombs: 15,
},
{
name: "中级",
width: 15,
bombs: 30,
},
{
name: "高级",
width: 20,
bombs: 70,
},
];
const flagIcon =
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 287.987 287.987" fill="#695ca8" style="enable-background:new 0 0 287.987 287.987;" xml:space="preserve"><g><path d="M228.702,141.029c-3.114-3.754-3.114-9.193,0-12.946l33.58-40.474c2.509-3.024,3.044-7.226,1.374-10.783 c-1.671-3.557-5.246-5.828-9.176-5.828h-57.647v60.98c0,16.618-13.52,30.138-30.138,30.138h-47.093v25.86 c0,5.599,4.539,10.138,10.138,10.138h124.74c3.93,0,7.505-2.271,9.176-5.828c1.671-3.557,1.135-7.759-1.374-10.783L228.702,141.029 z"/><path d="M176.832,131.978V25.138c0-5.599-4.539-10.138-10.138-10.138H53.37c0-8.284-6.716-15-15-15s-15,6.716-15,15 c0,7.827,0,253.91,0,257.987c0,8.284,6.716,15,15,15s15-6.716,15-15c0-6.943,0-126.106,0-130.871h113.324 C172.293,142.116,176.832,137.577,176.832,131.978z"/></g></svg>';
const bombIcon =
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 512 512" fill="#695ca8" ><g><path d="m411.313,123.313c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32-9.375,9.375-20.688-20.688c-12.484-12.5-32.766-12.5-45.25,0l-16,16c-1.261,1.261-2.304,2.648-3.31,4.051-21.739-8.561-45.324-13.426-70.065-13.426-105.867,0-192,86.133-192,192s86.133,192 192,192 192-86.133 192-192c0-24.741-4.864-48.327-13.426-70.065 1.402-1.007 2.79-2.049 4.051-3.31l16-16c12.5-12.492 12.5-32.758 0-45.25l-20.688-20.688 9.375-9.375 32.001-31.999zm-219.313,100.687c-52.938,0-96,43.063-96,96 0,8.836-7.164,16-16,16s-16-7.164-16-16c0-70.578 57.422-128 128-128 8.836,0 16,7.164 16,16s-7.164,16-16,16z"/><path d="m459.02,148.98c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l16,16c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16.001-16z"/><path d="m340.395,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16-16c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l15.999,16z"/><path d="m400,64c8.844,0 16-7.164 16-16v-32c0-8.836-7.156-16-16-16-8.844,0-16,7.164-16,16v32c0,8.836 7.156,16 16,16z"/><path d="m496,96.586h-32c-8.844,0-16,7.164-16,16 0,8.836 7.156,16 16,16h32c8.844,0 16-7.164 16-16 0-8.836-7.156-16-16-16z"/><path d="m436.98,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688l32-32c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32c-6.251,6.25-6.251,16.375-0.001,22.625z"/></g></svg>';
let root = document.documentElement;
let selectedLevel = levels[0];
let flags = 0;
let tiles = [];
let isGameOver = false;
let isTimerOn = false;
let timerCount = 0;
let finalTime = 0;
let numberColors = [
"#8B6AF5",
"#74c2f9",
"#42dfbc",
"#f9dd5b",
"#FEAC5E",
"#ff5d9e",
"#F29FF5",
"#c154d8",
];
let bgColors = [
"#b39ffd",
"#93c1fd",
"#8af1f8",
"#f9dd5b",
"#FEAC5E",
"#f87dae",
"#f6b8f8",
"#f7efce",
];
function getMainColor() {
const randomColor =
numberColors[Math.floor(Math.random() * numberColors.length)];
root.style.setProperty("--main-color", randomColor);
root.style.setProperty(
"--dark-color",
lightenDarkenColor(randomColor, -50)
);
}
function clearBoard() {
isGameOver = false;
flags = 0;
if (isTimerOn) stopTimer();
timerCount = 0;
// isTimerOn = false;
timer.innerHTML = "000";
tiles.forEach((tile) => {
tile.remove();
});
tiles = [];
container.classList.remove("shake");
bombHappyFace.classList.remove("show");
bombSadFace.classList.remove("show");
resultTime.classList.remove("show");
createBoard();
}
function setUp() {
createBackground();
createBoard();
}
function createBoard() {
getMainColor();
flagsLeft.innerHTML = selectedLevel.bombs;
const width = selectedLevel.width;
const tileWidth = parseInt(
getComputedStyle(root).getPropertyValue("--tile-width")
);
root.style.setProperty("--grid-width", width * tileWidth);
//add tiles
for (let i = 0; i < width * width; i++) {
const tile = document.createElement("div");
tile.setAttribute("id", i);
tile.classList.add("tile");
grid.appendChild(tile);
tiles.push(tile);
//left click
tile.addEventListener("click", () => clickTile(tile));
//ctrl and right click
tile.oncontextmenu = (e) => {
e.preventDefault();
addFlag(tile);
};
}
//add bombs
const randomTiles = tiles
.sort(() => Math.random() - 0.5)
.slice(0, selectedLevel.bombs)
.map((tile) => tile.id); // suffle tile's id and select 20 firsts to get 20 random tiles
tiles.forEach((tile) =>
randomTiles.includes(tile.id)
? tile.classList.add("has-bomb")
: tile.classList.add("is-empty")
); // if tile's id is in ramdomTile array, add bomb
tiles.sort((a, b) => a.id - b.id); //sort array by id again
//add numbers
for (let i = 0; i < tiles.length; i++) {
let total = 0;
const isLeftEdge = i % width === 0;
const isRightEdge = i % width === width - 1;
if (!tiles[i].classList.contains("has-bomb")) {
if (!isLeftEdge) {
if (tiles[i - 1] && tiles[i - 1].classList.contains("has-bomb"))
total++;
if (
tiles[i - 1 + width] &&
tiles[i - 1 + width].classList.contains("has-bomb")
)
total++;
if (
tiles[i - 1 - width] &&
tiles[i - 1 - width].classList.contains("has-bomb")
)
total++;
}
if (!isRightEdge) {
if (tiles[i + 1] && tiles[i + 1].classList.contains("has-bomb"))
total++;
if (
tiles[i + 1 + width] &&
tiles[i + 1 + width].classList.contains("has-bomb")
)
total++;
if (
tiles[i + 1 - width] &&
tiles[i + 1 - width].classList.contains("has-bomb")
)
total++;
}
if (tiles[i - width] && tiles[i - width].classList.contains("has-bomb"))
total++;
if (tiles[i + width] && tiles[i + width].classList.contains("has-bomb"))
total++;
tiles[i].setAttribute("data", total);
}
}
}
//click on tile
function clickTile(tile) {
if (!isTimerOn) startTimer();
let currentId = tile.id;
if (isGameOver) return;
if (tile.classList.contains("checked") || tile.classList.contains("flag"))
return;
if (tile.classList.contains("has-bomb")) {
gameOver(tile);
} else {
let total = tile.getAttribute("data");
if (total != 0) {
tile.classList.add("checked");
tile.style.color = numberColors[total - 1];
tile.style.textShadow =
"1px 1px" + lightenDarkenColor(numberColors[total - 1], -20);
tile.innerHTML = total;
return;
}
checktile(currentId);
}
tile.classList.add("checked");
}
//check neighboring tiles once tile is clicked
function checktile(currentId) {
const width = selectedLevel.width;
const isLeftEdge = currentId % width === 0;
const isRightEdge = currentId % width === width - 1;
const parsedId = parseInt(currentId);
function loopThroughtiles(tileId) {
const newId = tileId.id;
const newTile = document.getElementById(newId);
clickTile(newTile);
}
setTimeout(() => {
if (!isRightEdge) {
if (tiles[parsedId + 1 - width])
loopThroughtiles(tiles[parsedId + 1 - width]);
if (tiles[parsedId + 1]) loopThroughtiles(tiles[parsedId + 1]);
if (tiles[parsedId + 1 + width])
loopThroughtiles(tiles[parsedId + 1 + width]);
}
if (!isLeftEdge) {
if (tiles[parsedId - 1]) loopThroughtiles(tiles[parsedId - 1]);
if (tiles[parsedId - 1 - width])
loopThroughtiles(tiles[parsedId - 1 - width]);
if (tiles[parsedId - 1 + width])
loopThroughtiles(tiles[parsedId - 1 + width]);
}
if (tiles[parsedId - width]) loopThroughtiles(tiles[parsedId - width]);
if (tiles[parsedId + width]) loopThroughtiles(tiles[parsedId + width]);
}, 50);
}
//game over
function gameOver(currentTile) {
isGameOver = true;
stopTimer();
currentTile.innerHTML = bombIcon;
container.classList.add("shake");
currentTile.style.backgroundColor =
bgColors[Math.floor(Math.random() * bgColors.length)];
currentTile.classList.remove("has-bomb");
currentTile.classList.add("checked");
let itemsProcessed = 0;
//show all the bombs
const bombTiles = tiles.filter((tile) =>
tile.classList.contains("has-bomb")
);
bombTiles.forEach((tile, index) => {
setTimeout(() => {
tile.innerHTML = bombIcon;
tile.style.backgroundColor =
bgColors[Math.floor(Math.random() * bgColors.length)];
tile.classList.remove("has-bomb");
tile.classList.add("checked");
itemsProcessed++;
if (itemsProcessed === bombTiles.length) {
setTimeout(() => {
modal.classList.add("show");
bombSadFace.classList.add("show");
result.innerHTML = "游戏结束!";
}, 1000);
}
}, 10 * index);
});
}
//add Flag with right click
function addFlag(tile) {
if (isGameOver) return;
if (!tile.classList.contains("checked") && flags < selectedLevel.bombs) {
if (!tile.classList.contains("flag")) {
tile.classList.add("flag");
tile.innerHTML = flagIcon;
flags++;
flagsLeft.innerHTML = selectedLevel.bombs - flags;
checkForWin();
} else {
tile.classList.remove("flag");
tile.innerHTML = "";
flags--;
flagsLeft.innerHTML = selectedLevel.bombs - flags;
}
}
}
//check for win
function checkForWin() {
let matches = 0;
tiles.forEach((tile) => {
if (
tile.classList.contains("flag") &&
tile.classList.contains("has-bomb")
) {
matches++;
}
if (matches === selectedLevel.bombs) {
stopTimer();
modal.classList.add("show");
bombHappyFace.classList.add("show");
resultTime.classList.add("show");
result.innerHTML = "恭喜!";
isGameOver = true;
// reveal all remaining tiles
if (!tile.classList.contains("checked")) {
tile.classList.add("checked");
}
}
});
}
function replay() {
if (modal.classList.contains("show")) modal.classList.remove("show");
clearBoard();
}
document.querySelector("#new-game").addEventListener("click", replay);
// timer functions
function startTimer() {
isTimerOn = true;
let sec = 0;
timerCount = setInterval(() => {
sec++;
timer.innerHTML = ("00" + sec).slice(-3);
if (sec > 998) clearInterval(timerCount);
finalTime = sec;
}, 1000);
}
function stopTimer() {
clearInterval(timerCount);
isTimerOn = false;
finalTimeDisplay.innerHTML = finalTime;
}
// dropdown menu functions
const dropdownTitle = document.querySelector(".dropdown .title");
const dropdownOptions = document.querySelectorAll(".dropdown .option");
function toggleMenuDisplay(e) {
const dropdown = e.target.parentNode; //getting the parent selector
const menu = dropdown.querySelector(".menu"); //selecting 'menu' fron the parent selector
menu.classList.toggle("show");
}
function handleOptionSelected(e) {
e.target.parentNode.classList.toggle("show"); // using parentnode to get the menu elementfrom option elements
dropdownTitle.textContent = e.target.textContent;
// here: select current level form levels object
selectedLevel = levels.find((level) => level.name === e.target.textContent);
clearBoard();
}
// event listeners
dropdownTitle.addEventListener("click", toggleMenuDisplay);
dropdownOptions.forEach((option) =>
option.addEventListener("click", handleOptionSelected)
);
// bg functions
function addElement(x, y) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.setAttributeNS(
"http://www.w3.org/1999/xlink",
"xlink:href",
"#bomb-svg"
);
svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
svg.setAttribute("style", "top: " + y + "px; left: " + x + "px");
svg.appendChild(use);
background.appendChild(svg);
}
function createBackground() {
const spacing = 60;
const w = window.innerWidth;
const h = window.innerHeight;
for (let y = 0; y <= h; y += spacing) {
if (y % (spacing * 2) === 0) {
for (let x = 0; x <= w; x += spacing) {
addElement(x, y);
}
} else {
for (let x = -(spacing / 2); x <= w; x += spacing) {
addElement(x, y);
}
}
}
}
//helper function
function lightenDarkenColor(col, amt) {
let usePound = false;
if (col[0] == "#") {
col = col.slice(1);
usePound = true;
}
let num = parseInt(col, 16);
let r = (num >> 16) + amt;
if (r > 255) r = 255;
else if (r < 0) r = 0;
let b = ((num >> 8) & 0x00ff) + amt;
if (b > 255) b = 255;
else if (b < 0) b = 0;
let g = (num & 0x0000ff) + amt;
if (g > 255) g = 255;
else if (g < 0) g = 0;
return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16);
}
setUp();
});
</script>
</body>
</html>
没有回复内容