<!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 href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.0.0/css/all.min.css" rel="stylesheet">
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
background-color: #f0f2f5;
}
.board-cell {
width: 30px;
height: 30px;
position: relative;
cursor: pointer;
}
.board-cell::before {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 1px;
background-color: #000;
}
.board-cell::after {
content: '';
position: absolute;
left: 50%;
top: 0;
width: 1px;
height: 100%;
background-color: #000;
}
.chess-piece {
position: absolute;
width: 26px;
height: 26px;
border-radius: 50%;
z-index: 10;
top: 2px;
left: 2px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.chess-black {
background: radial-gradient(circle at 8px 8px, #666, #000);
}
.chess-white {
background: radial-gradient(circle at 8px 8px, #fff, #ccc);
}
.history-item:hover {
background-color: #f3f4f6;
}
.board-container {
background-color: #e3bc7a;
padding: 15px;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
/* 棋盘上的标记点 */
.mark-point::before {
content: '';
position: absolute;
width: 8px;
height: 8px;
background-color: #333;
border-radius: 50%;
z-index: 5;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.grid-cols-15 {
grid-template-columns: repeat(15, minmax(0, 1fr));
}
.last-move {
position: relative;
}
.last-move::after {
content: '';
position: absolute;
width: 10px;
height: 10px;
background-color: rgba(255, 0, 0, 0.5);
border-radius: 50%;
z-index: 15;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { transform: translate(-50%, -50%) scale(0.8); opacity: 0.7; }
50% { transform: translate(-50%, -50%) scale(1.2); opacity: 0.9; }
100% { transform: translate(-50%, -50%) scale(0.8); opacity: 0.7; }
}
</style>
</head>
<body class="bg-gray-100 min-h-screen py-6">
<div class="container mx-auto px-4">
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800">五子棋</h1>
<p class="text-gray-600 mt-2">经典智力游戏 - 五子连珠</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
<!-- 左侧设置面板 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-4 text-gray-800 flex items-center">
<i class="fas fa-cog mr-2"></i>游戏设置
</h2>
<div class="mb-6">
<p class="font-medium mb-2 text-gray-700">游戏模式</p>
<div class="flex space-x-2">
<button id="pve-mode" class="flex-1 px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-300 transition">
<i class="fas fa-robot mr-2"></i>人机对战
</button>
<button id="pvp-mode" class="flex-1 px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 transition">
<i class="fas fa-user-friends mr-2"></i>双人对战
</button>
</div>
</div>
<div id="difficulty-container" class="mb-6">
<p class="font-medium mb-2 text-gray-700">AI难度</p>
<div class="flex space-x-2">
<button id="easy-mode" class="flex-1 px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300 transition">
初级
</button>
<button id="medium-mode" class="flex-1 px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 transition">
中级
</button>
<button id="hard-mode" class="flex-1 px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 transition">
高级
</button>
</div>
</div>
<div class="mb-6">
<p class="font-medium mb-2 text-gray-700">当前状态</p>
<div id="game-status" class="p-3 bg-gray-100 rounded-md text-center font-medium">
黑方回合
</div>
</div>
<div class="flex space-x-3 mt-6">
<button id="undo-btn" class="flex-1 px-4 py-3 bg-yellow-500 text-white rounded-md hover:bg-yellow-600 focus:outline-none focus:ring-2 focus:ring-yellow-300 disabled:opacity-50 disabled:cursor-not-allowed transition flex items-center justify-center">
<i class="fas fa-undo mr-2"></i>悔棋
</button>
<button id="restart-btn" class="flex-1 px-4 py-3 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-300 transition flex items-center justify-center">
<i class="fas fa-redo mr-2"></i>重新开始
</button>
</div>
<div class="mt-8 bg-blue-50 p-4 rounded-md">
<h3 class="text-lg font-medium text-blue-800 mb-2">游戏规则</h3>
<ul class="text-sm text-blue-700 list-disc list-inside space-y-1">
<li>黑方先行,双方轮流落子</li>
<li>先形成五子连珠者获胜</li>
<li>连珠可以是横向、纵向或斜向</li>
</ul>
</div>
</div>
<!-- 中间棋盘区域 -->
<div class="board-container">
<div id="board" class="grid grid-cols-15 gap-0 mx-auto w-max"></div>
</div>
<!-- 右侧历史记录 -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-4 text-gray-800 flex items-center">
<i class="fas fa-history mr-2"></i>历史记录
</h2>
<div id="history-container" class="h-96 overflow-y-auto pr-1">
<div id="history-list" class="space-y-2"></div>
</div>
</div>
</div>
</div>
<footer class="mt-12 py-4 text-center text-gray-600 text-sm">
<p>© 2023 五子棋游戏 | 所有权利保留</p>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 游戏配置
const config = {
boardSize: 15,
gameMode: 'pve', // 'pve' 或 'pvp'
difficulty: 'easy', // 'easy', 'medium', 'hard'
currentPlayer: 'black', // 'black' 或 'white'
gameOver: false,
lastMove: null
};
// 游戏状态
let board = Array(config.boardSize).fill().map(() => Array(config.boardSize).fill(null));
let history = [];
let aiThinking = false;
// DOM元素
const boardElement = document.getElementById('board');
const gameStatus = document.getElementById('game-status');
const historyList = document.getElementById('history-list');
const undoBtn = document.getElementById('undo-btn');
const restartBtn = document.getElementById('restart-btn');
const pveMode = document.getElementById('pve-mode');
const pvpMode = document.getElementById('pvp-mode');
const easyMode = document.getElementById('easy-mode');
const mediumMode = document.getElementById('medium-mode');
const hardMode = document.getElementById('hard-mode');
const difficultyContainer = document.getElementById('difficulty-container');
// 初始化棋盘
function initBoard() {
boardElement.innerHTML = '';
board = Array(config.boardSize).fill().map(() => Array(config.boardSize).fill(null));
config.lastMove = null;
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
const cell = document.createElement('div');
cell.classList.add('board-cell');
cell.setAttribute('data-row', i);
cell.setAttribute('data-col', j);
// 添加标记点
if (
(i === 3 && j === 3) ||
(i === 3 && j === 11) ||
(i === 11 && j === 3) ||
(i === 11 && j === 11) ||
(i === 7 && j === 7)
) {
cell.classList.add('mark-point');
}
cell.addEventListener('click', () => handleCellClick(i, j));
boardElement.appendChild(cell);
}
}
updateGameStatus();
clearHistory();
}
// 处理单元格点击
function handleCellClick(row, col) {
// 如果游戏结束或AI正在思考,则不允许操作
if (config.gameOver || aiThinking) return;
// 如果单元格已经有棋子,则不允许落子
if (board[row][col] !== null) return;
// 落子
placePiece(row, col, config.currentPlayer);
// 检查胜负
if (checkWin(row, col, config.currentPlayer)) {
config.gameOver = true;
updateGameStatus(`${config.currentPlayer === 'black' ? '黑方' : '白方'}获胜!`);
return;
}
// 检查平局
if (checkDraw()) {
config.gameOver = true;
updateGameStatus('平局!');
return;
}
// 切换玩家
config.currentPlayer = config.currentPlayer === 'black' ? 'white' : 'black';
updateGameStatus();
// 如果是人机模式且轮到AI,则AI落子
if (config.gameMode === 'pve' && config.currentPlayer === 'white') {
aiThinking = true;
updateGameStatus('AI正在思考...');
// 延迟一下,模拟AI思考
setTimeout(() => {
makeAiMove();
aiThinking = false;
}, 800);
}
}
// AI落子
function makeAiMove() {
const move = getAiMove();
if (move) {
placePiece(move.row, move.col, 'white');
// 检查胜负
if (checkWin(move.row, move.col, 'white')) {
config.gameOver = true;
updateGameStatus('白方(AI)获胜!');
return;
}
// 检查平局
if (checkDraw()) {
config.gameOver = true;
updateGameStatus('平局!');
return;
}
// 切换玩家
config.currentPlayer = 'black';
updateGameStatus();
}
}
// 获取AI落子位置
function getAiMove() {
switch (config.difficulty) {
case 'easy':
return getEasyAiMove();
case 'medium':
return getMediumAiMove();
case 'hard':
return getHardAiMove();
default:
return getEasyAiMove();
}
}
// 简单AI:随机落子或寻找进攻/防守点
function getEasyAiMove() {
// 70%几率随机落子,30%几率使用中等难度AI策略
if (Math.random() < 0.7) {
return getRandomMove();
} else {
return getBasicStrategyMove();
}
}
// 中等AI:基本策略(进攻/防守)
function getMediumAiMove() {
return getBasicStrategyMove();
}
// 困难AI:使用评分策略
function getHardAiMove() {
return getBestMove();
}
// 随机落子(但优先选择靠近其他棋子的位置)
function getRandomMove() {
// 如果是第一步,优先下在中心位置附近
if (history.length === 1) {
const centerRow = 7;
const centerCol = 7;
const radius = 2;
const possibleMoves = [];
for (let i = Math.max(0, centerRow - radius); i <= Math.min(config.boardSize - 1, centerRow + radius); i++) {
for (let j = Math.max(0, centerCol - radius); j <= Math.min(config.boardSize - 1, centerCol + radius); j++) {
if (board[i][j] === null) {
possibleMoves.push({ row: i, col: j });
}
}
}
if (possibleMoves.length > 0) {
const randomIndex = Math.floor(Math.random() * possibleMoves.length);
return possibleMoves[randomIndex];
}
}
// 尝试在已有棋子周围落子
const adjacentMoves = [];
const availableMoves = [];
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null) {
availableMoves.push({ row: i, col: j });
// 检查周围是否有棋子
let hasAdjacentPiece = false;
for (let di = -1; di <= 1; di++) {
for (let dj = -1; dj <= 1; dj++) {
if (di === 0 && dj === 0) continue;
const ni = i + di;
const nj = j + dj;
if (ni >= 0 && ni < config.boardSize && nj >= 0 && nj < config.boardSize && board[ni][nj] !== null) {
hasAdjacentPiece = true;
break;
}
}
if (hasAdjacentPiece) break;
}
if (hasAdjacentPiece) {
adjacentMoves.push({ row: i, col: j });
}
}
}
}
if (adjacentMoves.length > 0) {
const randomIndex = Math.floor(Math.random() * adjacentMoves.length);
return adjacentMoves[randomIndex];
}
if (availableMoves.length > 0) {
const randomIndex = Math.floor(Math.random() * availableMoves.length);
return availableMoves[randomIndex];
}
return null;
}
// 基本策略:检查是否有即将连成五子的情况,进行进攻或防守
function getBasicStrategyMove() {
// 首先检查AI是否可以赢
const winMove = findWinningMove('white');
if (winMove) return winMove;
// 然后检查是否需要阻止对手赢
const blockMove = findWinningMove('black');
if (blockMove) return blockMove;
// 检查是否有四子连珠的进攻点
const attackMove = findNInRowMove('white', 4);
if (attackMove) return attackMove;
// 检查是否需要阻止对手四子连珠
const defendMove = findNInRowMove('black', 4);
if (defendMove) return defendMove;
// 检查是否有三子连珠的进攻点
const attack3Move = findNInRowMove('white', 3);
if (attack3Move) return attack3Move;
// 检查是否需要阻止对手三子连珠
const defend3Move = findNInRowMove('black', 3);
if (defend3Move) return defend3Move;
// 否则寻找一个好的位置
const goodMove = findGoodMove();
if (goodMove) return goodMove;
// 如果没找到好的位置,就随机落子
return getRandomMove();
}
// 寻找制胜点(连成五子的点)
function findWinningMove(player) {
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null) {
// 临时放置棋子
board[i][j] = player;
// 检查是否获胜
const isWinning = checkWin(i, j, player);
// 移除临时棋子
board[i][j] = null;
if (isWinning) {
return { row: i, col: j };
}
}
}
}
return null;
}
// 寻找N子连珠的点
function findNInRowMove(player, n) {
const opponent = player === 'black' ? 'white' : 'black';
let bestMove = null;
let bestScore = -1;
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null) {
const score = evaluateNInRow(i, j, player, opponent, n);
if (score > bestScore) {
bestScore = score;
bestMove = { row: i, col: j };
}
}
}
}
return bestScore > 0 ? bestMove : null;
}
// 评估某位置形成N子连珠的可能性
function evaluateNInRow(row, col, player, opponent, n) {
let score = 0;
const directions = [
{ dr: 0, dc: 1 }, // 水平
{ dr: 1, dc: 0 }, // 垂直
{ dr: 1, dc: 1 }, // 对角线(右下)
{ dr: 1, dc: -1 } // 对角线(左下)
];
board[row][col] = player; // 临时放置棋子
for (const dir of directions) {
let count = 1; // 包括当前位置
let blocked = 0; // 被对手棋子阻挡的端点数
// 检查一个方向
let r = row + dir.dr;
let c = col + dir.dc;
while (r >= 0 && r < config.boardSize && c >= 0 && c < config.boardSize && board[r][c] === player) {
count++;
r += dir.dr;
c += dir.dc;
}
// 检查这个方向是否被对手棋子阻挡
if (r >= 0 && r < config.boardSize && c >= 0 && c < config.boardSize && board[r][c] === opponent) {
blocked++;
}
// 检查相反方向
r = row - dir.dr;
c = col - dir.dc;
while (r >= 0 && r < config.boardSize && c >= 0 && c < config.boardSize && board[r][c] === player) {
count++;
r -= dir.dr;
c -= dir.dc;
}
// 检查这个方向是否被对手棋子阻挡
if (r >= 0 && r < config.boardSize && c >= 0 && c < config.boardSize && board[r][c] === opponent) {
blocked++;
}
// 如果恰好有n个连续棋子且至少有一端是开放的
if (count === n && blocked < 2) {
score += (blocked === 0) ? 2 : 1; // 双向开放价值更高
}
}
board[row][col] = null; // 恢复空白
return score;
}
// 寻找一个好的位置
function findGoodMove() {
let bestScore = -1;
let bestMove = null;
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null) {
const score = evaluatePosition(i, j);
if (score > bestScore) {
bestScore = score;
bestMove = { row: i, col: j };
}
}
}
}
return bestMove;
}
// 评估位置分数
function evaluatePosition(row, col) {
// 这里简单评估周围8个方向的棋子情况
let score = 0;
const directions = [
{ dr: -1, dc: 0 }, // 上
{ dr: 1, dc: 0 }, // 下
{ dr: 0, dc: -1 }, // 左
{ dr: 0, dc: 1 }, // 右
{ dr: -1, dc: -1 }, // 左上
{ dr: -1, dc: 1 }, // 右上
{ dr: 1, dc: -1 }, // 左下
{ dr: 1, dc: 1 } // 右下
];
// 检查每个方向
for (const dir of directions) {
// 检查AI(白方)的连续棋子
score += countInDirection(row, col, dir.dr, dir.dc, 'white') * 2;
// 检查对手(黑方)的连续棋子
score += countInDirection(row, col, dir.dr, dir.dc, 'black');
}
// 棋盘中心位置更优先
const distanceToCenter = Math.abs(row - 7) + Math.abs(col - 7);
score += (10 - distanceToCenter) / 2;
return score;
}
// 计算某个方向上的连续棋子数
function countInDirection(row, col, dr, dc, player) {
let count = 0;
let r = row + dr;
let c = col + dc;
// 检查一个方向
while (r >= 0 && r < config.boardSize && c >= 0 && c < config.boardSize && board[r][c] === player) {
count++;
r += dr;
c += dc;
}
// 检查反方向
r = row - dr;
c = col - dc;
while (r >= 0 && r < config.boardSize && c >= 0 && c < config.boardSize && board[r][c] === player) {
count++;
r -= dr;
c -= dc;
}
return count;
}
// 获取最佳落子位置(高级AI)
function getBestMove() {
const winMove = findWinningMove('white');
if (winMove) return winMove;
const blockMove = findWinningMove('black');
if (blockMove) return blockMove;
let bestScore = -Infinity;
let bestMove = null;
// 创建一个评估范围,只评估离已有棋子较近的位置
const evaluationCells = [];
const maxDistance = 2; // 最大距离
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null) {
// 检查是否在已有棋子附近
let nearExistingPiece = false;
outerLoop:
for (let di = -maxDistance; di <= maxDistance; di++) {
for (let dj = -maxDistance; dj <= maxDistance; dj++) {
const ni = i + di;
const nj = j + dj;
if (ni >= 0 && ni < config.boardSize && nj >= 0 && nj < config.boardSize && board[ni][nj] !== null) {
nearExistingPiece = true;
break outerLoop;
}
}
}
if (nearExistingPiece) {
evaluationCells.push({ row: i, col: j });
}
}
}
}
// 如果没有找到任何靠近已有棋子的位置,就评估所有空白位置
if (evaluationCells.length === 0) {
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null) {
evaluationCells.push({ row: i, col: j });
}
}
}
}
// 对所有候选位置进行评估
for (const cell of evaluationCells) {
board[cell.row][cell.col] = 'white';
let score = minimax(board, 3, false, -Infinity, Infinity);
board[cell.row][cell.col] = null;
if (score > bestScore) {
bestScore = score;
bestMove = { row: cell.row, col: cell.col };
}
}
return bestMove || getRandomMove();
}
// Minimax算法(带Alpha-Beta剪枝)
function minimax(board, depth, isMaximizing, alpha, beta) {
// 检查终止条件
if (depth === 0) {
return evaluateBoard();
}
// 检查是否有人获胜
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] !== null) {
if (checkWin(i, j, board[i][j])) {
return board[i][j] === 'white' ? 1000000 : -1000000;
}
}
}
}
if (isMaximizing) {
let maxScore = -Infinity;
// 只考虑离现有棋子较近的位置
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null && hasNearbyPiece(i, j, 2)) {
board[i][j] = 'white';
let score = minimax(board, depth - 1, false, alpha, beta);
board[i][j] = null;
maxScore = Math.max(maxScore, score);
alpha = Math.max(alpha, score);
if (beta <= alpha) {
return maxScore;
}
}
}
}
return maxScore;
} else {
let minScore = Infinity;
// 只考虑离现有棋子较近的位置
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null && hasNearbyPiece(i, j, 2)) {
board[i][j] = 'black';
let score = minimax(board, depth - 1, true, alpha, beta);
board[i][j] = null;
minScore = Math.min(minScore, score);
beta = Math.min(beta, score);
if (beta <= alpha) {
return minScore;
}
}
}
}
return minScore;
}
}
// 检查某个位置周围是否有棋子
function hasNearbyPiece(row, col, distance) {
for (let i = Math.max(0, row - distance); i <= Math.min(config.boardSize - 1, row + distance); i++) {
for (let j = Math.max(0, col - distance); j <= Math.min(config.boardSize - 1, col + distance); j++) {
if (board[i][j] !== null) {
return true;
}
}
}
return false;
}
// 评估整个棋盘情况
function evaluateBoard() {
let score = 0;
// 横向评估
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize - 4; j++) {
score += evaluateLine(board[i][j], board[i][j+1], board[i][j+2], board[i][j+3], board[i][j+4]);
}
}
// 纵向评估
for (let i = 0; i < config.boardSize - 4; i++) {
for (let j = 0; j < config.boardSize; j++) {
score += evaluateLine(board[i][j], board[i+1][j], board[i+2][j], board[i+3][j], board[i+4][j]);
}
}
// 对角线评估(左上到右下)
for (let i = 0; i < config.boardSize - 4; i++) {
for (let j = 0; j < config.boardSize - 4; j++) {
score += evaluateLine(board[i][j], board[i+1][j+1], board[i+2][j+2], board[i+3][j+3], board[i+4][j+4]);
}
}
// 对角线评估(右上到左下)
for (let i = 0; i < config.boardSize - 4; i++) {
for (let j = 4; j < config.boardSize; j++) {
score += evaluateLine(board[i][j], board[i+1][j-1], board[i+2][j-2], board[i+3][j-3], board[i+4][j-4]);
}
}
return score;
}
// 评估一行棋子的分数
function evaluateLine(a, b, c, d, e) {
// 计算白棋和黑棋的数量
let whiteCount = [a, b, c, d, e].filter(cell => cell === 'white').length;
let blackCount = [a, b, c, d, e].filter(cell => cell === 'black').length;
let emptyCount = 5 - whiteCount - blackCount;
// 如果同时有白棋和黑棋,则这条线上不可能形成五子连珠
if (whiteCount > 0 && blackCount > 0) {
return 0;
}
// 根据棋子数量给分
if (whiteCount === 5) return 100000; // AI赢了
if (blackCount === 5) return -100000; // 玩家赢了
if (whiteCount === 4 && emptyCount === 1) return 10000; // AI差一颗棋子就赢了
if (blackCount === 4 && emptyCount === 1) return -10000; // 玩家差一颗棋子就赢了
if (whiteCount === 3 && emptyCount === 2) return 1000; // AI有3颗连珠
if (blackCount === 3 && emptyCount === 2) return -1000; // 玩家有3颗连珠
if (whiteCount === 2 && emptyCount === 3) return 100; // AI有2颗连珠
if (blackCount === 2 && emptyCount === 3) return -100; // 玩家有2颗连珠
if (whiteCount === 1 && emptyCount === 4) return 10; // AI有1颗棋子
if (blackCount === 1 && emptyCount === 4) return -10; // 玩家有1颗棋子
return 0;
}
// 落子
function placePiece(row, col, player) {
board[row][col] = player;
// 移除上一次落子提示
if (config.lastMove !== null) {
const lastCell = document.querySelector(`.board-cell[data-row="${config.lastMove.row}"][data-col="${config.lastMove.col}"]`);
lastCell.classList.remove('last-move');
}
// 添加棋子到UI
const cell = document.querySelector(`.board-cell[data-row="${row}"][data-col="${col}"]`);
const piece = document.createElement('div');
piece.classList.add('chess-piece');
piece.classList.add(player === 'black' ? 'chess-black' : 'chess-white');
cell.appendChild(piece);
// 标记此次落子
cell.classList.add('last-move');
config.lastMove = { row: row, col: col };
// 添加到历史记录
addToHistory(row, col, player);
}
// 添加到历史记录
function addToHistory(row, col, player) {
const moveNumber = history.length + 1;
const move = {
number: moveNumber,
row: row,
col: col,
player: player
};
history.push(move);
// 更新历史记录UI
const historyItem = document.createElement('div');
historyItem.classList.add('history-item', 'p-2', 'rounded', 'flex', 'items-center', 'justify-between', 'border-b', 'border-gray-100');
const moveText = document.createElement('span');
// 使用字母和数字表示坐标,更符合围棋记谱习惯
const colLabel = String.fromCharCode(65 + col); // A, B, C, ...
moveText.textContent = `${moveNumber}. ${player === 'black' ? '黑' : '白'} [${colLabel}${row+1}]`;
const pieceIndicator = document.createElement('div');
pieceIndicator.classList.add('w-4', 'h-4', 'rounded-full');
pieceIndicator.classList.add(player === 'black' ? 'bg-black' : 'bg-white', 'border', 'border-gray-300');
historyItem.appendChild(moveText);
historyItem.appendChild(pieceIndicator);
historyList.appendChild(historyItem);
// 滚动到底部
historyList.scrollTop = historyList.scrollHeight;
// 启用悔棋按钮
undoBtn.disabled = false;
}
// 清空历史记录
function clearHistory() {
history = [];
historyList.innerHTML = '';
undoBtn.disabled = true;
}
// 悔棋
function undoMove() {
if (history.length === 0 || config.gameOver) return;
// 人机模式下需要撤销两步(玩家和AI各一步)
if (config.gameMode === 'pve') {
// 如果只有一步(玩家刚走的),或者轮到AI但还没走
if (history.length === 1 || (history.length % 2 === 1 && config.currentPlayer === 'white')) {
// 只撤销一步
const lastMove = history.pop();
board[lastMove.row][lastMove.col] = null;
const cell = document.querySelector(`.board-cell[data-row="${lastMove.row}"][data-col="${lastMove.col}"]`);
cell.classList.remove('last-move');
if (cell.lastChild) {
cell.removeChild(cell.lastChild);
}
config.currentPlayer = lastMove.player;
config.lastMove = history.length > 0 ? { row: history[history.length - 1].row, col: history[history.length - 1].col } : null;
// 如果还有上一步,标记为最后一步
if (config.lastMove !== null) {
const prevCell = document.querySelector(`.board-cell[data-row="${config.lastMove.row}"][data-col="${config.lastMove.col}"]`);
prevCell.classList.add('last-move');
}
} else {
// 撤销两步
for (let i = 0; i < 2; i++) {
if (history.length > 0) {
const lastMove = history.pop();
board[lastMove.row][lastMove.col] = null;
const cell = document.querySelector(`.board-cell[data-row="${lastMove.row}"][data-col="${lastMove.col}"]`);
cell.classList.remove('last-move');
if (cell.lastChild) {
cell.removeChild(cell.lastChild);
}
// 只在第一次撤销时更新当前玩家
if (i === 0) {
config.currentPlayer = lastMove.player;
}
}
}
config.lastMove = history.length > 0 ? { row: history[history.length - 1].row, col: history[history.length - 1].col } : null;
// 如果还有上一步,标记为最后一步
if (config.lastMove !== null) {
const prevCell = document.querySelector(`.board-cell[data-row="${config.lastMove.row}"][data-col="${config.lastMove.col}"]`);
prevCell.classList.add('last-move');
}
}
} else {
// 双人模式只撤销一步
const lastMove = history.pop();
board[lastMove.row][lastMove.col] = null;
const cell = document.querySelector(`.board-cell[data-row="${lastMove.row}"][data-col="${lastMove.col}"]`);
cell.classList.remove('last-move');
if (cell.lastChild) {
cell.removeChild(cell.lastChild);
}
config.currentPlayer = lastMove.player;
config.lastMove = history.length > 0 ? { row: history[history.length - 1].row, col: history[history.length - 1].col } : null;
// 如果还有上一步,标记为最后一步
if (config.lastMove !== null) {
const prevCell = document.querySelector(`.board-cell[data-row="${config.lastMove.row}"][data-col="${config.lastMove.col}"]`);
prevCell.classList.add('last-move');
}
}
// 更新历史记录UI
if (historyList.lastChild) {
historyList.removeChild(historyList.lastChild);
}
// 如果没有历史记录了,禁用悔棋按钮
if (history.length === 0) {
undoBtn.disabled = true;
config.lastMove = null;
}
// 重置游戏状态
config.gameOver = false;
updateGameStatus();
}
// 更新游戏状态显示
function updateGameStatus(message) {
if (message) {
gameStatus.textContent = message;
return;
}
if (config.gameMode === 'pve') {
if (config.currentPlayer === 'black') {
gameStatus.textContent = '你的回合(黑方)';
} else {
gameStatus.textContent = 'AI回合(白方)';
}
} else {
if (config.currentPlayer === 'black') {
gameStatus.textContent = '黑方回合';
} else {
gameStatus.textContent = '白方回合';
}
}
}
// 检查是否获胜
function checkWin(row, col, player) {
const directions = [
{ dr: 0, dc: 1 }, // 水平
{ dr: 1, dc: 0 }, // 垂直
{ dr: 1, dc: 1 }, // 对角线(右下)
{ dr: 1, dc: -1 } // 对角线(左下)
];
for (const dir of directions) {
let count = 1; // 包括当前位置
// 检查一个方向
let r = row + dir.dr;
let c = col + dir.dc;
while (r >= 0 && r < config.boardSize && c >= 0 && c < config.boardSize && board[r][c] === player) {
count++;
r += dir.dr;
c += dir.dc;
}
// 检查相反方向
r = row - dir.dr;
c = col - dir.dc;
while (r >= 0 && r < config.boardSize && c >= 0 && c < config.boardSize && board[r][c] === player) {
count++;
r -= dir.dr;
c -= dir.dc;
}
if (count >= 5) {
return true;
}
}
return false;
}
// 检查是否平局
function checkDraw() {
for (let i = 0; i < config.boardSize; i++) {
for (let j = 0; j < config.boardSize; j++) {
if (board[i][j] === null) {
return false;
}
}
}
return true;
}
// 事件监听器
undoBtn.addEventListener('click', undoMove);
restartBtn.addEventListener('click', () => {
initBoard();
config.gameOver = false;
config.currentPlayer = 'black';
});
pveMode.addEventListener('click', () => {
if (config.gameMode !== 'pve') {
config.gameMode = 'pve';
pveMode.classList.add('bg-blue-500', 'text-white');
pveMode.classList.remove('bg-gray-200', 'text-gray-700');
pvpMode.classList.add('bg-gray-200', 'text-gray-700');
pvpMode.classList.remove('bg-blue-500', 'text-white');
difficultyContainer.classList.remove('hidden');
initBoard();
}
});
pvpMode.addEventListener('click', () => {
if (config.gameMode !== 'pvp') {
config.gameMode = 'pvp';
pvpMode.classList.add('bg-blue-500', 'text-white');
pvpMode.classList.remove('bg-gray-200', 'text-gray-700');
pveMode.classList.add('bg-gray-200', 'text-gray-700');
pveMode.classList.remove('bg-blue-500', 'text-white');
difficultyContainer.classList.add('hidden');
initBoard();
}
});
easyMode.addEventListener('click', () => {
if (config.difficulty !== 'easy') {
config.difficulty = 'easy';
easyMode.classList.add('bg-green-500', 'text-white');
easyMode.classList.remove('bg-gray-200', 'text-gray-700');
mediumMode.classList.add('bg-gray-200', 'text-gray-700');
mediumMode.classList.remove('bg-green-500', 'text-white');
hardMode.classList.add('bg-gray-200', 'text-gray-700');
hardMode.classList.remove('bg-green-500', 'text-white');
initBoard();
}
});
mediumMode.addEventListener('click', () => {
if (config.difficulty !== 'medium') {
config.difficulty = 'medium';
mediumMode.classList.add('bg-green-500', 'text-white');
mediumMode.classList.remove('bg-gray-200', 'text-gray-700');
easyMode.classList.add('bg-gray-200', 'text-gray-700');
easyMode.classList.remove('bg-green-500', 'text-white');
hardMode.classList.add('bg-gray-200', 'text-gray-700');
hardMode.classList.remove('bg-green-500', 'text-white');
initBoard();
}
});
hardMode.addEventListener('click', () => {
if (config.difficulty !== 'hard') {
config.difficulty = 'hard';
hardMode.classList.add('bg-green-500', 'text-white');
hardMode.classList.remove('bg-gray-200', 'text-gray-700');
easyMode.classList.add('bg-gray-200', 'text-gray-700');
easyMode.classList.remove('bg-green-500', 'text-white');
mediumMode.classList.add('bg-gray-200', 'text-gray-700');
mediumMode.classList.remove('bg-green-500', 'text-white');
initBoard();
}
});
// 初始化游戏
initBoard();
});
</script>
</body>
</html>
基于JS实现的前端五子棋代码
最新推荐文章于 2025-06-10 23:00:08 发布