<!
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
>五子棋AI大师</
title
>
<
style
>
body {
font-family: 'Arial', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
background-color: #f5f5f5;
margin: 0;
padding: 10px;
touch-action: manipulation;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 1.5rem;
}
.game-container {
display: flex;
flex-direction: column;
width: 100%;
max-width: 600px;
margin-top: 10px;
}
.board-container {
position: relative;
width: 100%;
margin-bottom: 10px;
}
#board {
display: grid;
grid-template-columns: repeat(15, 1fr);
grid-template-rows: repeat(15, 1fr);
aspect-ratio: 1/1;
background-color: #dcb35c;
border: 2px solid #8d6e3a;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.cell {
position: relative;
cursor: pointer;
}
.cell::before, .cell::after {
content: '';
position: absolute;
background-color: #000;
}
.cell::before {
width: 100%;
height: 1px;
top: 50%;
left: 0;
transform: translateY(-50%);
}
.cell::after {
width: 1px;
height: 100%;
left: 50%;
top: 0;
transform: translateX(-50%);
}
.cell.star::before, .cell.star::after {
width: 2px;
height: 2px;
background-color: #000;
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.stone {
position: absolute;
width: 80%;
height: 80%;
border-radius: 50%;
top: 10%;
left: 10%;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
z-index: 1;
}
.stone.black {
background: radial-gradient(circle at 30% 30%, #666, #000);
}
.stone.white {
background: radial-gradient(circle at 30% 30%, #fff, #ccc);
}
.stone.last-move {
animation: pulse 1.5s infinite;
}
.stone.last-move::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
border: 2px dashed rgba(0, 0, 255, 0.7);
border-radius: 50%;
top: -2px;
left: -2px;
box-sizing: border-box;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.panel {
width: 100%;
background-color: #fff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 15px;
}
select, button {
padding: 8px 12px;
border-radius: 4px;
border: 1px solid #ddd;
font-size: 14px;
flex: 1;
min-width: 120px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.status {
margin-top: 15px;
padding: 10px;
border-radius: 4px;
text-align: center;
font-weight: bold;
font-size: 0.9rem;
}
.timer {
margin-top: 10px;
font-size: 0.8rem;
color: #666;
text-align: center;
}
.thinking {
color: #e74c3c;
font-weight: bold;
}
.win-line {
position: absolute;
background-color: rgba(255, 0, 0, 0.7);
z-index: 2;
transform-origin: 0 0;
}
.game-info {
margin-top: 15px;
font-size: 0.8rem;
color: #666;
}
.game-info p {
margin: 5px 0;
}
[url=home.php?mod=space&uid=945662]@media[/url] (min-width: 768px) {
.game-container {
flex-direction: row;
max-width: 1000px;
gap: 20px;
}
.board-container {
width: auto;
margin-bottom: 0;
}
#board {
width: 600px;
height: 600px;
}
.panel {
width: 300px;
}
h1 {
font-size: 2rem;
}
}
</
style
>
</
head
>
<
body
>
<
h1
>五子棋AI大师</
h1
>
<
div
class
=
"game-container"
>
<
div
class
=
"board-container"
>
<
div
id
=
"board"
></
div
>
</
div
>
<
div
class
=
"panel"
>
<
div
class
=
"controls"
>
<
select
id
=
"color-select"
>
<
option
value
=
"black"
>执黑先行</
option
>
<
option
value
=
"white"
>执白后行</
option
>
</
select
>
<
button
id
=
"start-btn"
>开始游戏</
button
>
<
button
id
=
"restart-btn"
disabled>重新开始</
button
>
</
div
>
<
div
id
=
"status"
class
=
"status"
>请选择执棋颜色并开始游戏</
div
>
<
div
id
=
"timer"
class
=
"timer"
></
div
>
<
div
class
=
"game-info"
>
<
p
><
strong
>游戏规则:</
strong
></
p
>
<
p
>· 无禁手规则</
p
>
<
p
>· 横、竖、斜先连成五子者胜</
p
>
<
p
>· AI思考时间不超过10秒</
p
>
</
div
>
</
div
>
</
div
>
<
script
>
// 游戏常量
const BOARD_SIZE = 15;
const EMPTY = 0;
const BLACK = 1;
const WHITE = 2;
const HUMAN = 1;
const AI = 2;
const MAX_THINKING_TIME = 10000; // 10秒
// 游戏状态
let gameState = {
board: Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(EMPTY)),
currentPlayer: BLACK,
gameOver: false,
humanColor: BLACK,
aiColor: WHITE,
lastMove: null,
thinking: false,
thinkingStart: 0,
timerInterval: null,
winLine: null
};
// DOM元素
const boardElement = document.getElementById('board');
const colorSelect = document.getElementById('color-select');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
const statusElement = document.getElementById('status');
const timerElement = document.getElementById('timer');
// 初始化棋盘
function initializeBoard() {
boardElement.innerHTML = '';
// 创建棋盘格子
for (let row = 0; row <
BOARD_SIZE
; row++) {
for (let
col
=
0
; col < BOARD_SIZE; col++) {
const
cell
=
document
.createElement('div');
cell.className
=
'cell'
;
// 标记星位
if ((row === 3 || row === 7 || row === 11) && (col === 3 || col === 7 || col === 11)) {
cell.classList.add('star');
}
cell.dataset.row
= row;
cell.dataset.col
= col;
cell.addEventListener('click', () => handleCellClick(row, col));
boardElement.appendChild(cell);
}
}
}
// 开始游戏
function startGame() {
// 重置游戏状态
gameState.board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(EMPTY));
gameState.gameOver = false;
gameState.lastMove = null;
gameState.humanColor = colorSelect.value === 'black' ? BLACK : WHITE;
gameState.aiColor = gameState.humanColor === BLACK ? WHITE : BLACK;
gameState.currentPlayer = BLACK; // 总是黑棋先手
// 清除之前的胜利线和棋子
if (gameState.winLine) {
gameState.winLine.remove();
gameState.winLine = null;
}
document.querySelectorAll('.stone').forEach(stone => stone.remove());
// 更新UI
statusElement.textContent = gameState.currentPlayer === gameState.humanColor ?
'轮到您下棋' : 'AI思考中...';
statusElement.style.backgroundColor = '#f8f9fa';
startBtn.disabled = true;
restartBtn.disabled = false;
// 如果是AI先手,则AI下棋
if (gameState.currentPlayer === gameState.aiColor) {
makeAIMove();
}
}
// 处理格子点击
function handleCellClick(row, col) {
if (gameState.gameOver || gameState.thinking || gameState.currentPlayer !== gameState.humanColor) {
return;
}
if (gameState.board[row][col] === EMPTY) {
placeStone(row, col, gameState.humanColor);
gameState.currentPlayer = gameState.aiColor;
if (!checkWin(row, col, gameState.humanColor)) {
statusElement.textContent = 'AI思考中...';
setTimeout(() => makeAIMove(), 500); // 延迟500ms让玩家看到自己的棋子
}
}
}
// 放置棋子
function placeStone(row, col, color) {
gameState.board[row][col] = color;
gameState.lastMove = { row, col };
// 更新UI
const cell = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
const stone = document.createElement('div');
stone.className = `stone ${color === BLACK ? 'black' : 'white'}`;
// 移除上一个最后一步的标记
document.querySelectorAll('.stone.last-move').forEach(s => s.classList.remove('last-move'));
// 标记最后一步
stone.classList.add('last-move');
cell.appendChild(stone);
}
// AI走棋
function makeAIMove() {
if (gameState.gameOver) return;
gameState.thinking = true;
gameState.thinkingStart = Date.now();
// 显示思考倒计时
timerElement.textContent = 'AI思考中: 0.0s';
timerElement.classList.add('thinking');
const timerInterval = setInterval(() => {
const elapsed = (Date.now() - gameState.thinkingStart) / 1000;
timerElement.textContent = `AI思考中: ${elapsed.toFixed(1)}s`;
}, 100);
gameState.timerInterval = timerInterval;
// 使用setTimeout确保UI更新
setTimeout(() => {
const startTime = Date.now();
const { row, col } = findBestMove();
const thinkTime = Date.now() - startTime;
// 确保思考时间不超过最大限制
if (thinkTime <
MAX_THINKING_TIME
) {
setTimeout(() => {
placeStone(row, col, gameState.aiColor);
gameState.currentPlayer = gameState.humanColor;
if (!checkWin(row, col, gameState.aiColor)) {
statusElement.textContent = '轮到您下棋';
}
finishAITurn();
}, Math.max(0, 500 - thinkTime)); // 确保至少有500ms的动画时间
} else {
// 如果超时,随机走一步
const emptyCells = [];
for (let r = 0; r <
BOARD_SIZE
; r++) {
for (let
c
=
0
; c < BOARD_SIZE; c++) {
if (gameState.board[r][c] === EMPTY) {
emptyCells.push({ row: r, col: c });
}
}
}
if (emptyCells.length > 0) {
const randomMove = emptyCells[Math.floor(Math.random() * emptyCells.length)];
placeStone(randomMove.row, randomMove.col, gameState.aiColor);
gameState.currentPlayer = gameState.humanColor;
statusElement.textContent = '轮到您下棋';
}
finishAITurn();
}
}, 100);
}
// 完成AI回合
function finishAITurn() {
gameState.thinking = false;
clearInterval(gameState.timerInterval);
timerElement.textContent = '';
timerElement.classList.remove('thinking');
}
// 检查胜利条件
function checkWin(row, col, color) {
const directions = [
[0, 1], // 水平
[1, 0], // 垂直
[1, 1], // 对角线
[1, -1] // 反对角线
];
for (const [dx, dy] of directions) {
let count = 1;
// 正向检查
for (let i = 1; i <
5
; i++) {
const
r
=
row
+ i * dx;
const
c
=
col
+ i * dy;
if (r < 0 || r >= BOARD_SIZE || c <
0
|| c >= BOARD_SIZE || gameState.board[r][c] !== color) {
break;
}
count++;
}
// 反向检查
for (let i = 1; i <
5
; i++) {
const
r
=
row
- i * dx;
const
c
=
col
- i * dy;
if (r < 0 || r >= BOARD_SIZE || c <
0
|| c >= BOARD_SIZE || gameState.board[r][c] !== color) {
break;
}
count++;
}
if (count >= 5) {
gameOver(color, row, col, dx, dy);
return true;
}
}
// 检查平局
if (isBoardFull()) {
gameOver(EMPTY);
return true;
}
return false;
}
// 游戏结束处理
function gameOver(winner, row, col, dx, dy) {
gameState.gameOver = true;
if (winner === EMPTY) {
statusElement.textContent = '游戏结束:平局';
statusElement.style.backgroundColor = '#fff3cd';
} else {
const isHumanWin = winner === gameState.humanColor;
statusElement.textContent = isHumanWin ? '恭喜您获胜!' : 'AI获胜!';
statusElement.style.backgroundColor = isHumanWin ? '#d4edda' : '#f8d7da';
// 绘制胜利线
if (row !== undefined && col !== undefined && dx !== undefined && dy !== undefined) {
drawWinLine(row, col, dx, dy);
}
}
}
// 绘制胜利线
function drawWinLine(row, col, dx, dy) {
// 计算线的起点和终点
let startRow = row;
let startCol = col;
let endRow = row;
let endCol = col;
// 找到线的起点
for (let i = 1; i <
5
; i++) {
const
r
=
row
- i * dx;
const
c
=
col
- i * dy;
if (r < 0 || r >= BOARD_SIZE || c <
0
|| c >= BOARD_SIZE || gameState.board[r][c] !== gameState.board[row][col]) {
break;
}
startRow = r;
startCol = c;
}
// 找到线的终点
for (let i = 1; i <
5
; i++) {
const
r
=
row
+ i * dx;
const
c
=
col
+ i * dy;
if (r < 0 || r >= BOARD_SIZE || c <
0
|| c >= BOARD_SIZE || gameState.board[r][c] !== gameState.board[row][col]) {
break;
}
endRow = r;
endCol = c;
}
// 创建线元素
const line = document.createElement('div');
line.className = 'win-line';
// 计算线的位置和尺寸
const boardRect = boardElement.getBoundingClientRect();
const cellSize = boardRect.width / BOARD_SIZE;
const startX = startCol * cellSize + cellSize / 2;
const startY = startRow * cellSize + cellSize / 2;
const endX = endCol * cellSize + cellSize / 2;
const endY = endRow * cellSize + cellSize / 2;
const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
line.style.width = `${length}px`;
line.style.height = '3px';
line.style.left = `${startX}px`;
line.style.top = `${startY}px`;
line.style.transform = `rotate(${angle}deg)`;
boardElement.parentNode.appendChild(line);
gameState.winLine = line;
}
// 检查棋盘是否已满
function isBoardFull() {
for (let row = 0; row <
BOARD_SIZE
; row++) {
for (let
col
=
0
; col < BOARD_SIZE; col++) {
if (gameState.board[row][col] === EMPTY) {
return false;
}
}
}
return true;
}
// 评估函数 - 评估当前棋盘对指定玩家的优势
function evaluateBoard(board, player) {
const
opponent
=
player
=== BLACK ? WHITE : BLACK;
let
score
=
0
;
// 检查所有可能的五子连线
for (let
row
=
0
; row < BOARD_SIZE; row++) {
for (let
col
=
0
; col < BOARD_SIZE; col++) {
// 水平方向
if (col <= BOARD_SIZE - 5) {
const line = [
board[row][col],
board[row][col + 1],
board[row][col + 2],
board[row][col + 3],
board[row][col + 4]
];
score += evaluateLine(line, player, opponent);
}
// 垂直方向
if (row <= BOARD_SIZE - 5) {
const line = [
board[row][col],
board[row + 1][col],
board[row + 2][col],
board[row + 3][col],
board[row + 4][col]
];
score += evaluateLine(line, player, opponent);
}
// 对角线方向
if (row <= BOARD_SIZE - 5 && col <= BOARD_SIZE - 5) {
const line = [
board[row][col],
board[row + 1][col + 1],
board[row + 2][col + 2],
board[row + 3][col + 3],
board[row + 4][col + 4]
];
score += evaluateLine(line, player, opponent);
}
// 反对角线方向
if (row <= BOARD_SIZE - 5 && col >= 4) {
const line = [
board[row][col],
board[row + 1][col - 1],
board[row + 2][col - 2],
board[row + 3][col - 3],
board[row + 4][col - 4]
];
score += evaluateLine(line, player, opponent);
}
}
}
return score;
}
// 评估一行五子的分数
function evaluateLine(line, player, opponent) {
let playerCount = 0;
let opponentCount = 0;
let emptyCount = 0;
for (const cell of line) {
if (cell === player) playerCount++;
else if (cell === opponent) opponentCount++;
else emptyCount++;
}
// 如果同时包含双方棋子,没有价值
if (playerCount > 0 && opponentCount > 0) {
return 0;
}
// 根据连子数量评分
if (playerCount === 5) return 1000000; // 五连,胜利
if (opponentCount === 5) return -1000000; // 对手五连,阻止
if (playerCount === 4 && emptyCount === 1) return 10000; // 活四
if (opponentCount === 4 && emptyCount === 1) return -10000; // 对手活四
if (playerCount === 3 && emptyCount === 2) return 1000; // 活三
if (opponentCount === 3 && emptyCount === 2) return -1000; // 对手活三
if (playerCount === 2 && emptyCount === 3) return 100; // 活二
if (opponentCount === 2 && emptyCount === 3) return -100; // 对手活二
if (playerCount === 1 && emptyCount === 4) return 10; // 活一
if (opponentCount === 1 && emptyCount === 4) return -10; // 对手活一
return 0;
}
// 寻找最佳移动 - 使用极大极小算法与Alpha-Beta剪枝
function findBestMove() {
const startTime = Date.now();
let bestScore = -Infinity;
let bestMove = null;
const depth = 3; // 搜索深度
// 获取所有可能的移动
const moves = getPossibleMoves(gameState.board);
// 如果没有移动,返回null
if (moves.length === 0) {
return { row: Math.floor(BOARD_SIZE / 2), col: Math.floor(BOARD_SIZE / 2) };
}
// 遍历所有可能的移动
for (const move of moves) {
const { row, col } = move;
// 尝试这个移动
gameState.board[row][col] = gameState.aiColor;
// 评估这个移动
const score = minimax(gameState.board, depth - 1, -Infinity, Infinity, false, startTime);
// 撤销移动
gameState.board[row][col] = EMPTY;
// 更新最佳移动
if (score > bestScore || bestMove === null) {
bestScore = score;
bestMove = { row, col };
}
// 如果已经超时,立即返回当前最佳移动
if (Date.now() - startTime > MAX_THINKING_TIME - 100) {
break;
}
}
return bestMove || moves[0]; // 如果没有找到最佳移动,返回第一个可能的移动
}
// 极大极小算法与Alpha-Beta剪枝
function minimax(board, depth, alpha, beta, isMaximizing, startTime) {
// 检查是否超时
if (Date.now() - startTime > MAX_THINKING_TIME - 100) {
return 0;
}
// 检查游戏是否结束或达到最大深度
const winner = checkTerminal(board);
if (winner !== null || depth === 0) {
if (winner === gameState.aiColor) return 1000000;
if (winner === gameState.humanColor) return -1000000;
return evaluateBoard(board, gameState.aiColor);
}
// 获取所有可能的移动
const moves = getPossibleMoves(board);
if (isMaximizing) {
let maxScore = -Infinity;
for (const move of moves) {
const { row, col } = move;
board[row][col] = gameState.aiColor;
const score = minimax(board, depth - 1, alpha, beta, false, startTime);
board[row][col] = EMPTY;
maxScore = Math.max(maxScore, score);
alpha = Math.max(alpha, score);
if (beta <= alpha) {
break; // Beta剪枝
}
}
return maxScore;
} else {
let minScore = Infinity;
for (const move of moves) {
const { row, col } = move;
board[row][col] = gameState.humanColor;
const score = minimax(board, depth - 1, alpha, beta, true, startTime);
board[row][col] = EMPTY;
minScore = Math.min(minScore, score);
beta = Math.min(beta, score);
if (beta <= alpha) {
break; // Alpha剪枝
}
}
return minScore;
}
}
// 检查游戏是否结束
function checkTerminal(board) {
// 检查所有可能的五子连线
for (let row = 0; row <
BOARD_SIZE
; row++) {
for (let
col
=
0
; col < BOARD_SIZE; col++) {
if (board[row][col] === EMPTY) continue;
const
color
=
board
[row][col];
// 水平方向
if (col <= BOARD_SIZE - 5) {
let
win
=
true
;
for (let
i
=
1
; i < 5; i++) {
if (board[row][col + i] !== color) {
win
=
false
;
break;
}
}
if (win) return color;
}
// 垂直方向
if (row <= BOARD_SIZE - 5) {
let
win
=
true
;
for (let
i
=
1
; i < 5; i++) {
if (board[row + i][col] !== color) {
win
=
false
;
break;
}
}
if (win) return color;
}
// 对角线方向
if (row <= BOARD_SIZE - 5 && col <= BOARD_SIZE - 5) {
let
win
=
true
;
for (let
i
=
1
; i < 5; i++) {
if (board[row + i][col + i] !== color) {
win
=
false
;
break;
}
}
if (win) return color;
}
// 反对角线方向
if (row <= BOARD_SIZE - 5 && col >= 4) {
let win = true;
for (let i = 1; i <
5
; i++) {
if (board[row + i][col - i] !== color) {
win
=
false
;
break;
}
}
if (win) return color;
}
}
}
// 检查平局
let
isFull
=
true
;
for (let
row
=
0
; row < BOARD_SIZE; row++) {
for (let
col
=
0
; col < BOARD_SIZE; col++) {
if (board[row][col] === EMPTY) {
isFull
=
false
;
break;
}
}
if (!isFull) break;
}
return isFull ? EMPTY : null;
}
// 获取所有可能的移动(优化:只考虑已有棋子周围的空位)
function getPossibleMoves(board) {
const moves = [];
const directions = [
[-1, -1], [-1, 0], [-1, 1],
[0, -1], [0, 1],
[1, -1], [1, 0], [1, 1]
];
// 先检查棋盘上是否有棋子
let
hasStone
=
false
;
for (let
row
=
0
; row < BOARD_SIZE; row++) {
for (let
col
=
0
; col < BOARD_SIZE; col++) {
if (board[row][col] !== EMPTY) {
hasStone
=
true
;
break;
}
}
if (hasStone) break;
}
// 如果棋盘为空,返回中心点
if (!hasStone) {
return [{ row: Math.floor(BOARD_SIZE / 2), col: Math.floor(BOARD_SIZE / 2) }];
}
// 否则,收集所有已有棋子周围的空位
const
considered
=
Array
(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(false));
for (let row = 0; row <
BOARD_SIZE
; row++) {
for (let
col
=
0
; col < BOARD_SIZE; col++) {
if (board[row][col] !== EMPTY) {
// 检查周围的8个方向
for (const [dx, dy] of directions) {
const
newRow
=
row
+ dx;
const
newCol
=
col
+ dy;
if (newRow >= 0 && newRow <
BOARD_SIZE
&& newCol >= 0 && newCol <
BOARD_SIZE
&&
board[newRow][newCol] === EMPTY && !considered[newRow][newCol]) {
moves.push({ row: newRow, col: newCol });
considered[newRow][newCol] = true;
}
}
}
}
}
// 如果没有找到可能的移动(理论上不应该发生),返回所有空位
if (moves.length === 0) {
for (let
row
=
0
; row < BOARD_SIZE; row++) {
for (let
col
=
0
; col < BOARD_SIZE; col++) {
if (board[row][col] === EMPTY) {
moves.push({ row, col });
}
}
}
}
return moves;
}
// 事件监听
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
// 初始化
initializeBoard();
</script>
</
body
>
</
html
>