当前位置: 首页 > news >正文

【HTML】五子棋(精美版)

目录

第一版:双人对战

执行代码

运行结果

第二版本:人机对战

执行代码

优化点

第三版:人机对战

 执行代码

运行结果 

优化点

第四版:人机对战

执行代码

运行结果

优化点


第一版:双人对战

执行代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>五子棋</title><style>body {display: flex;flex-direction: column;align-items: center;font-family: Arial, sans-serif;}canvas {border: 1px solid black;}#status {margin-top: 10px;font-size: 18px;}#restart {margin-top: 10px;padding: 10px 20px;font-size: 16px;cursor: pointer;}</style>
</head>
<body><canvas id="board" width="450" height="450"></canvas><div id="status">当前玩家:黑棋</div><button id="restart">重新开始</button><script>const canvas = document.getElementById('board');const ctx = canvas.getContext('2d');const status = document.getElementById('status');const restartBtn = document.getElementById('restart');const size = 15; // 15x15 棋盘const cellSize = 30; // 每个格子大小const board = Array(size).fill().map(() => Array(size).fill(0)); // 0:空, 1:黑棋, 2:白棋let currentPlayer = 1; // 1:黑棋, 2:白棋let gameOver = false;// 绘制棋盘function drawBoard() {ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.beginPath();for (let i = 0; i < size; i++) {// 画横线ctx.moveTo(0, i * cellSize);ctx.lineTo(canvas.width, i * cellSize);// 画竖线ctx.moveTo(i * cellSize, 0);ctx.lineTo(i * cellSize, canvas.height);}ctx.strokeStyle = '#000';ctx.stroke();// 绘制棋子for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 1) {ctx.beginPath();ctx.arc(j * cellSize, i * cellSize, cellSize / 2 - 2, 0, Math.PI * 2);ctx.fillStyle = 'black';ctx.fill();} else if (board[i][j] === 2) {ctx.beginPath();ctx.arc(j * cellSize, i * cellSize, cellSize / 2 - 2, 0, Math.PI * 2);ctx.fillStyle = 'white';ctx.strokeStyle = 'black';ctx.fill();ctx.stroke();}}}}// 检查胜利function checkWin(row, col, player) {const directions = [[0, 1], [1, 0], [1, 1], [1, -1] // 水平、垂直、斜线];for (let [dx, dy] of directions) {let count = 1;// 正方向for (let i = 1; i < 5; i++) {let newRow = row + i * dx;let newCol = col + i * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {break;}}// 反方向for (let i = 1; i < 5; i++) {let newRow = row - i * dx;let newCol = col - i * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {break;}}if (count >= 5) return true;}return false;}// 处理点击canvas.addEventListener('click', (e) => {if (gameOver) return;const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;const row = Math.round(y / cellSize);const col = Math.round(x / cellSize);if (row >= 0 && row < size && col >= 0 && col < size && board[row][col] === 0) {board[row][col] = currentPlayer;drawBoard();if (checkWin(row, col, currentPlayer)) {status.textContent = `游戏结束!${currentPlayer === 1 ? '黑棋' : '白棋'}获胜!`;gameOver = true;} else {currentPlayer = currentPlayer === 1 ? 2 : 1;status.textContent = `当前玩家:${currentPlayer === 1 ? '黑棋' : '白棋'}`;}}});// 重新开始restartBtn.addEventListener('click', () => {board.forEach(row => row.fill(0));currentPlayer = 1;gameOver = false;status.textContent = '当前玩家:黑棋';drawBoard();});// 初始化棋盘drawBoard();</script>
</body>
</html>

运行结果


第二版本:人机对战

执行代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>五子棋 - 人机对战</title><style>body {display: flex;flex-direction: column;align-items: center;font-family: Arial, sans-serif;}canvas {border: 1px solid black;}#status {margin-top: 10px;font-size: 18px;}#timer {margin-top: 5px;font-size: 16px;color: red;}#restart {margin-top: 10px;padding: 10px 20px;font-size: 16px;cursor: pointer;}</style>
</head>
<body><canvas id="board" width="450" height="450"></canvas><div id="status">当前玩家:黑棋(玩家)</div><div id="timer">剩余时间:20秒</div><button id="restart">重新开始</button><script>const canvas = document.getElementById('board');const ctx = canvas.getContext('2d');const status = document.getElementById('status');const timerDisplay = document.getElementById('timer');const restartBtn = document.getElementById('restart');const size = 15; // 15x15 棋盘const cellSize = 30; // 每个格子大小const board = Array(size).fill().map(() => Array(size).fill(0)); // 0:空, 1:黑棋, 2:白棋let currentPlayer = 1; // 1:黑棋(玩家), 2:白棋(AI)let gameOver = false;let timer;let timeLeft = 20; // 玩家20秒,AI 5秒// 绘制棋盘function drawBoard() {ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.beginPath();for (let i = 0; i < size; i++) {ctx.moveTo(0, i * cellSize);ctx.lineTo(canvas.width, i * cellSize);ctx.moveTo(i * cellSize, 0);ctx.lineTo(i * cellSize, canvas.height);}ctx.strokeStyle = '#000';ctx.stroke();for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 1) {ctx.beginPath();ctx.arc(j * cellSize, i * cellSize, cellSize / 2 - 2, 0, Math.PI * 2);ctx.fillStyle = 'black';ctx.fill();} else if (board[i][j] === 2) {ctx.beginPath();ctx.arc(j * cellSize, i * cellSize, cellSize / 2 - 2, 0, Math.PI * 2);ctx.fillStyle = 'white';ctx.strokeStyle = 'black';ctx.fill();ctx.stroke();}}}}// 检查胜利function checkWin(row, col, player) {const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];for (let [dx, dy] of directions) {let count = 1;for (let i = 1; i < 5; i++) {let newRow = row + i * dx;let newCol = col + i * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {break;}}for (let i = 1; i < 5; i++) {let newRow = row - i * dx;let newCol = col - i * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {break;}}if (count >= 5) return true;}return false;}// 评估棋盘得分function evaluateBoard() {let score = 0;for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 0) continue;const player = board[i][j];const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];for (let [dx, dy] of directions) {let count = 1;let openEnds = 0;for (let k = 1; k < 5; k++) {let newRow = i + k * dx;let newCol = j + k * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === 0) {openEnds++;}break;}}for (let k = 1; k < 5; k++) {let newRow = i - k * dx;let newCol = j - k * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === 0) {openEnds++;}break;}}if (count >= 5) return player === 2 ? Infinity : -Infinity;if (player === 2) {if (count === 4 && openEnds >= 1) score += 10000;else if (count === 3 && openEnds === 2) score += 1000;else if (count === 3 && openEnds === 1) score += 100;else if (count === 2 && openEnds === 2) score += 10;} else {if (count === 4 && openEnds >= 1) score -= 12000;else if (count === 3 && openEnds === 2) score -= 1200;else if (count === 3 && openEnds === 1) score -= 120;else if (count === 2 && openEnds === 2) score -= 12;}}}}return score;}// 获取可落子位置(优化:仅考虑邻近棋子的空位)function getValidMoves() {const moves = [];for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 0) {let hasNeighbor = false;for (let di = -1; di <= 1; di++) {for (let dj = -1; dj <= 1; dj++) {let ni = i + di, nj = j + dj;if (ni >= 0 && ni < size && nj >= 0 && nj < size && board[ni][nj] !== 0) {hasNeighbor = true;break;}}if (hasNeighbor) break;}if (hasNeighbor || (i === Math.floor(size/2) && j === Math.floor(size/2))) {moves.push([i, j]);}}}}return moves;}// Minimax 算法 + Alpha-Beta 剪枝function minimax(depth, alpha, beta, isMaximizing) {if (depth === 0) return evaluateBoard();let validMoves = getValidMoves();if (validMoves.length === 0) return evaluateBoard();if (isMaximizing) {let maxEval = -Infinity;for (let [row, col] of validMoves) {board[row][col] = 2;if (checkWin(row, col, 2)) {board[row][col] = 0;return Infinity;}let evalScore = minimax(depth - 1, alpha, beta, false);board[row][col] = 0;maxEval = Math.max(maxEval, evalScore);alpha = Math.max(alpha, evalScore);if (beta <= alpha) break;}return maxEval;} else {let minEval = Infinity;for (let [row, col] of validMoves) {board[row][col] = 1;if (checkWin(row, col, 1)) {board[row][col] = 0;return -Infinity;}let evalScore = minimax(depth - 1, alpha, beta, true);board[row][col] = 0;minEval = Math.min(minEval, evalScore);beta = Math.min(beta, evalScore);if (beta <= alpha) break;}return minEval;}}// AI 选择最佳落子function aiMove() {let bestScore = -Infinity;let bestMove = null;const validMoves = getValidMoves();const depth = 2;const startTime = performance.now();for (let [row, col] of validMoves) {board[row][col] = 2;let score = minimax(depth, -Infinity, Infinity, false);board[row][col] = 0;if (score > bestScore) {bestScore = score;bestMove = [row, col];}if (performance.now() - startTime > 4500) break; // 限制 AI 计算时间}if (bestMove) {const [row, col] = bestMove;board[row][col] = 2;drawBoard();clearInterval(timer);if (checkWin(row, col, 2)) {status.textContent = '游戏结束!白棋(AI)获胜!';timerDisplay.textContent = '';gameOver = true;} else {currentPlayer = 1;status.textContent = '当前玩家:黑棋(玩家)';startTimer(20); // 玩家 20 秒}} else {// AI 超时或无合法落子clearInterval(timer);status.textContent = '游戏结束!白棋(AI)超时或无合法落子,黑棋(玩家)获胜!';timerDisplay.textContent = '';gameOver = true;}}// 计时器function startTimer(seconds) {timeLeft = seconds;timerDisplay.textContent = `剩余时间:${timeLeft}秒`;clearInterval(timer);timer = setInterval(() => {timeLeft--;timerDisplay.textContent = `剩余时间:${timeLeft}秒`;if (timeLeft <= 0) {clearInterval(timer);if (currentPlayer === 1) {status.textContent = '游戏结束!黑棋(玩家)超时,白棋(AI)获胜!';timerDisplay.textContent = '';gameOver = true;} else {status.textContent = '游戏结束!白棋(AI)超时,黑棋(玩家)获胜!';timerDisplay.textContent = '';gameOver = true;}}}, 1000);}// 处理玩家点击canvas.addEventListener('click', (e) => {if (gameOver || currentPlayer !== 1) return;const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;const row = Math.round(y / cellSize);const col = Math.round(x / cellSize);if (row >= 0 && row < size && col >= 0 && col < size && board[row][col] === 0) {board[row][col] = 1;drawBoard();clearInterval(timer);if (checkWin(row, col, 1)) {status.textContent = '游戏结束!黑棋(玩家)获胜!';timerDisplay.textContent = '';gameOver = true;} else {currentPlayer = 2;status.textContent = 'AI(白棋)思考中...';timerDisplay.textContent = '剩余时间:5秒';startTimer(5); // AI 5 秒setTimeout(() => {aiMove();}, 500);}}});// 重新开始restartBtn.addEventListener('click', () => {board.forEach(row => row.fill(0));currentPlayer = 1;gameOver = false;status.textContent = '当前玩家:黑棋(玩家)';timerDisplay.textContent = '剩余时间:20秒';clearInterval(timer);drawBoard();startTimer(20); // 玩家 20 秒});// 初始化棋盘并启动计时器drawBoard();startTimer(20); // 玩家 20 秒</script>
</body>
</html>

优化点

  • 玩家超时(20 秒未落子)会导致 AI 获胜,AI 超时(5 秒未落子)会导致玩家获胜。
  • AI 使用 minimax 算法(深度 2)结合 alpha-beta 剪枝,计算时间受限以确保 5 秒内完成。
  • 计时器通过 setInterval 实现,每秒更新,AI 计算时间通过 performance.now() 监控,限制在 4.5 秒内以留出余量。

第三版:人机对战

 执行代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>五子棋 - 人机对战</title><style>body {display: flex;flex-direction: column;align-items: center;font-family: Arial, sans-serif;}canvas {border: 1px solid black;}#status {margin-top: 10px;font-size: 18px;}#timer {margin-top: 5px;font-size: 16px;color: red;}#buttons {margin-top: 10px;}#undo, #restart {padding: 10px 20px;font-size: 16px;cursor: pointer;margin: 0 5px;}#undo:disabled {cursor: not-allowed;opacity: 0.5;}</style>
</head>
<body><canvas id="board" width="450" height="450"></canvas><div id="status">当前玩家:黑棋(玩家)</div><div id="timer">剩余时间:20秒</div><div id="buttons"><button id="undo">悔棋(剩余3次)</button><button id="restart">重新开始</button></div><script>const canvas = document.getElementById('board');const ctx = canvas.getContext('2d');const status = document.getElementById('status');const timerDisplay = document.getElementById('timer');const undoBtn = document.getElementById('undo');const restartBtn = document.getElementById('restart');const size = 15; // 15x15 棋盘const cellSize = 30; // 每个格子大小const board = Array(size).fill().map(() => Array(size).fill(0)); // 0:空, 1:黑棋, 2:白棋let currentPlayer = 1; // 1:黑棋(玩家), 2:白棋(AI)let gameOver = false;let timer;let timeLeft = 20; // 玩家20秒,AI 5秒let undoCount = 3; // 悔棋次数let moveHistory = []; // 存储落子历史let lastMove = null; // 最新落子位置// 绘制棋盘function drawBoard() {ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.beginPath();for (let i = 0; i < size; i++) {ctx.moveTo(0, i * cellSize);ctx.lineTo(canvas.width, i * cellSize);ctx.moveTo(i * cellSize, 0);ctx.lineTo(i * cellSize, canvas.height);}ctx.strokeStyle = '#000';ctx.stroke();for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 1) {ctx.beginPath();ctx.arc(j * cellSize, i * cellSize, cellSize / 2 - 2, 0, Math.PI * 2);ctx.fillStyle = 'black';ctx.fill();} else if (board[i][j] === 2) {ctx.beginPath();ctx.arc(j * cellSize, i * cellSize, cellSize / 2 - 2, 0, Math.PI * 2);ctx.fillStyle = 'white';ctx.strokeStyle = 'black';ctx.fill();ctx.stroke();}}}// 高亮最新落子if (lastMove) {const [row, col] = lastMove;ctx.beginPath();ctx.rect(col * cellSize - cellSize / 2, row * cellSize - cellSize / 2, cellSize, cellSize);ctx.strokeStyle = 'red';ctx.lineWidth = 2;ctx.stroke();ctx.lineWidth = 1; // 重置线宽setTimeout(() => {drawBoard(); // 2秒后清除高亮}, 2000);}}// 检查胜利function checkWin(row, col, player) {const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];for (let [dx, dy] of directions) {let count = 1;for (let i = 1; i < 5; i++) {let newRow = row + i * dx;let newCol = col + i * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {break;}}for (let i = 1; i < 5; i++) {let newRow = row - i * dx;let newCol = col - i * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {break;}}if (count >= 5) return true;}return false;}// 评估棋盘得分function evaluateBoard() {let score = 0;for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 0) continue;const player = board[i][j];const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];for (let [dx, dy] of directions) {let count = 1;let openEnds = 0;for (let k = 1; k < 5; k++) {let newRow = i + k * dx;let newCol = j + k * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === 0) {openEnds++;}break;}}for (let k = 1; k < 5; k++) {let newRow = i - k * dx;let newCol = j - k * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === 0) {openEnds++;}break;}}if (count >= 5) return player === 2 ? Infinity : -Infinity;if (player === 2) {if (count === 4 && openEnds >= 1) score += 10000;else if (count === 3 && openEnds === 2) score += 1000;else if (count === 3 && openEnds === 1) score += 100;else if (count === 2 && openEnds === 2) score += 10;} else {if (count === 4 && openEnds >= 1) score -= 12000;else if (count === 3 && openEnds === 2) score -= 1200;else if (count === 3 && openEnds === 1) score -= 120;else if (count === 2 && openEnds === 2) score -= 12;}}}}return score;}// 获取可落子位置function getValidMoves() {const moves = [];for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 0) {let hasNeighbor = false;for (let di = -1; di <= 1; di++) {for (let dj = -1; dj <= 1; dj++) {let ni = i + di, nj = j + dj;if (ni >= 0 && ni < size && nj >= 0 && nj < size && board[ni][nj] !== 0) {hasNeighbor = true;break;}}if (hasNeighbor) break;}if (hasNeighbor || (i === Math.floor(size/2) && j === Math.floor(size/2))) {moves.push([i, j]);}}}}return moves;}// Minimax 算法 + Alpha-Beta 剪枝function minimax(depth, alpha, beta, isMaximizing) {if (depth === 0) return evaluateBoard();let validMoves = getValidMoves();if (validMoves.length === 0) return evaluateBoard();if (isMaximizing) {let maxEval = -Infinity;for (let [row, col] of validMoves) {board[row][col] = 2;if (checkWin(row, col, 2)) {board[row][col] = 0;return Infinity;}let evalScore = minimax(depth - 1, alpha, beta, false);board[row][col] = 0;maxEval = Math.max(maxEval, evalScore);alpha = Math.max(alpha, evalScore);if (beta <= alpha) break;}return maxEval;} else {let minEval = Infinity;for (let [row, col] of validMoves) {board[row][col] = 1;if (checkWin(row, col, 1)) {board[row][col] = 0;return -Infinity;}let evalScore = minimax(depth - 1, alpha, beta, true);board[row][col] = 0;minEval = Math.min(minEval, evalScore);beta = Math.min(beta, evalScore);if (beta <= alpha) break;}return minEval;}}// AI 选择最佳落子function aiMove() {let bestScore = -Infinity;let bestMove = null;const validMoves = getValidMoves();const depth = 2;const startTime = performance.now();for (let [row, col] of validMoves) {board[row][col] = 2;let score = minimax(depth, -Infinity, Infinity, false);board[row][col] = 0;if (score > bestScore) {bestScore = score;bestMove = [row, col];}if (performance.now() - startTime > 4500) break;}if (bestMove) {const [row, col] = bestMove;board[row][col] = 2;moveHistory.push({ player: 2, row, col });lastMove = [row, col];drawBoard();clearInterval(timer);status.textContent = `白棋(AI)落子:(${row}, ${col})`;if (checkWin(row, col, 2)) {status.textContent = `游戏结束!白棋(AI)获胜!`;timerDisplay.textContent = '';gameOver = true;} else {currentPlayer = 1;setTimeout(() => {status.textContent = '当前玩家:黑棋(玩家)';startTimer(20);}, 2000);}} else {clearInterval(timer);status.textContent = '游戏结束!白棋(AI)超时或无合法落子,黑棋(玩家)获胜!';timerDisplay.textContent = '';gameOver = true;}}// 计时器function startTimer(seconds) {timeLeft = seconds;timerDisplay.textContent = `剩余时间:${timeLeft}秒`;clearInterval(timer);timer = setInterval(() => {timeLeft--;timerDisplay.textContent = `剩余时间:${timeLeft}秒`;if (timeLeft <= 0) {clearInterval(timer);if (currentPlayer === 1) {status.textContent = '游戏结束!黑棋(玩家)超时,白棋(AI)获胜!';timerDisplay.textContent = '';gameOver = true;} else {status.textContent = '游戏结束!白棋(AI)超时,黑棋(玩家)获胜!';timerDisplay.textContent = '';gameOver = true;}}}, 1000);}// 处理玩家点击canvas.addEventListener('click', (e) => {if (gameOver || currentPlayer !== 1) return;const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;const row = Math.round(y / cellSize);const col = Math.round(x / cellSize);if (row >= 0 && row < size && col >= 0 && col < size && board[row][col] === 0) {board[row][col] = 1;moveHistory.push({ player: 1, row, col });lastMove = [row, col];drawBoard();clearInterval(timer);status.textContent = `黑棋(玩家)落子:(${row}, ${col})`;if (checkWin(row, col, 1)) {status.textContent = `游戏结束!黑棋(玩家)获胜!`;timerDisplay.textContent = '';gameOver = true;} else {currentPlayer = 2;setTimeout(() => {status.textContent = 'AI(白棋)思考中...';timerDisplay.textContent = '剩余时间:5秒';startTimer(5);setTimeout(() => {aiMove();}, 500);}, 2000);}}});// 悔棋undoBtn.addEventListener('click', () => {if (gameOver || undoCount <= 0 || currentPlayer !== 1 || moveHistory.length < 2) return;// 撤销最后两次落子(AI 和玩家各一次)for (let i = 0; i < 2; i++) {if (moveHistory.length > 0) {const last = moveHistory.pop();board[last.row][last.col] = 0;}}undoCount--;undoBtn.textContent = `悔棋(剩余${undoCount}次)`;if (undoCount === 0) undoBtn.disabled = true;lastMove = moveHistory.length > 0 ? [moveHistory[moveHistory.length - 1].row, moveHistory[moveHistory.length - 1].col] : null;drawBoard();clearInterval(timer);status.textContent = '当前玩家:黑棋(玩家)';startTimer(20);});// 重新开始restartBtn.addEventListener('click', () => {board.forEach(row => row.fill(0));currentPlayer = 1;gameOver = false;undoCount = 3;moveHistory = [];lastMove = null;undoBtn.textContent = `悔棋(剩余${undoCount}次)`;undoBtn.disabled = false;status.textContent = '当前玩家:黑棋(玩家)';timerDisplay.textContent = '剩余时间:20秒';clearInterval(timer);drawBoard();startTimer(20);});// 初始化棋盘并启动计时器drawBoard();startTimer(20);</script>
</body>
</html>

运行结果 

优化点

  • 落子提示:最新落子以红色边框高亮 2 秒,状态栏显示落子坐标,确保玩家清楚落子位置。
  • 悔棋限制:最多 3 次,需在玩家回合使用,撤销最近的玩家和 AI 落子,保持游戏公平。

第四版:人机对战

执行代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>五子棋 - 人机对战</title><script src="https://cdn.tailwindcss.com"></script><style>body {background: linear-gradient(to bottom, #f0f4f8, #d9e2ec);min-height: 100vh;display: flex;justify-content: center;align-items: center;font-family: 'Arial', sans-serif;}canvas {background: url('https://www.transparenttextures.com/patterns/wood-pattern.png');border: 4px solid #4a3728;border-radius: 8px;box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);}</style>
</head>
<body><div class="flex flex-col items-center p-6"><h1 class="text-3xl font-bold text-gray-800 mb-4">五子棋 - 人机对战</h1><canvas id="board" width="480" height="480" class="mb-4"></canvas><div id="status" class="text-xl text-gray-700 mb-2">当前玩家:黑棋(玩家)</div><div id="timer" class="text-lg text-red-600 mb-4">剩余时间:20秒</div><div id="buttons" class="flex space-x-4"><button id="undo" class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed">悔棋(剩余3次)</button><button id="restart" class="bg-green-500 text-white px-6 py-2 rounded-lg hover:bg-green-600">重新开始</button></div></div><script>const canvas = document.getElementById('board');const ctx = canvas.getContext('2d');const status = document.getElementById('status');const timerDisplay = document.getElementById('timer');const undoBtn = document.getElementById('undo');const restartBtn = document.getElementById('restart');const size = 15; // 15x15 棋盘const cellSize = 30; // 每个格子大小const offset = 30; // 边缘偏移,确保棋子完整显示const board = Array(size).fill().map(() => Array(size).fill(0)); // 0:空, 1:黑棋, 2:白棋let currentPlayer = 1; // 1:黑棋(玩家), 2:白棋(AI)let gameOver = false;let timer;let timeLeft = 20; // 玩家20秒,AI 5秒let undoCount = 3; // 悔棋次数let moveHistory = []; // 落子历史let lastMove = null; // 最新落子// 绘制棋盘function drawBoard() {ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.beginPath();for (let i = 0; i < size; i++) {// 横线ctx.moveTo(offset, offset + i * cellSize);ctx.lineTo(offset + (size - 1) * cellSize, offset + i * cellSize);// 竖线ctx.moveTo(offset + i * cellSize, offset);ctx.lineTo(offset + i * cellSize, offset + (size - 1) * cellSize);}ctx.strokeStyle = '#000';ctx.lineWidth = 1;ctx.stroke();// 绘制棋子for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 1) {ctx.beginPath();ctx.arc(offset + j * cellSize, offset + i * cellSize, cellSize / 2 - 2, 0, Math.PI * 2);ctx.fillStyle = 'black';ctx.fill();} else if (board[i][j] === 2) {ctx.beginPath();ctx.arc(offset + j * cellSize, offset + i * cellSize, cellSize / 2 - 2, 0, Math.PI * 2);ctx.fillStyle = 'white';ctx.strokeStyle = 'black';ctx.fill();ctx.stroke();}}}// 高亮最新落子if (lastMove) {const [row, col] = lastMove;ctx.beginPath();ctx.rect(offset + col * cellSize - cellSize / 2, offset + row * cellSize - cellSize / 2, cellSize, cellSize);ctx.strokeStyle = 'red';ctx.lineWidth = 2;ctx.stroke();ctx.lineWidth = 1;setTimeout(() => {drawBoard();}, 2000);}}// 检查胜利function checkWin(row, col, player) {const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];for (let [dx, dy] of directions) {let count = 1;for (let i = 1; i < 5; i++) {let newRow = row + i * dx;let newCol = col + i * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {break;}}for (let i = 1; i < 5; i++) {let newRow = row - i * dx;let newCol = col - i * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {break;}}if (count >= 5) return true;}return false;}// 评估棋盘得分function evaluateBoard() {let score = 0;for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 0) continue;const player = board[i][j];const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];for (let [dx, dy] of directions) {let count = 1;let openEnds = 0;for (let k = 1; k < 5; k++) {let newRow = i + k * dx;let newCol = j + k * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === 0) {openEnds++;}break;}}for (let k = 1; k < 5; k++) {let newRow = i - k * dx;let newCol = j - k * dy;if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === player) {count++;} else {if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size && board[newRow][newCol] === 0) {openEnds++;}break;}}if (count >= 5) return player === 2 ? Infinity : -Infinity;if (player === 2) {if (count === 4 && openEnds >= 1) score += 10000;else if (count === 3 && openEnds === 2) score += 1000;else if (count === 3 && openEnds === 1) score += 100;else if (count === 2 && openEnds === 2) score += 10;} else {if (count === 4 && openEnds >= 1) score -= 12000;else if (count === 3 && openEnds === 2) score -= 1200;else if (count === 3 && openEnds === 1) score -= 120;else if (count === 2 && openEnds === 2) score -= 12;}}}}return score;}// 获取可落子位置function getValidMoves() {const moves = [];for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {if (board[i][j] === 0) {let hasNeighbor = false;for (let di = -1; di <= 1; di++) {for (let dj = -1; dj <= 1; dj++) {let ni = i + di, nj = j + dj;if (ni >= 0 && ni < size && nj >= 0 && nj < size && board[ni][nj] !== 0) {hasNeighbor = true;break;}}if (hasNeighbor) break;}if (hasNeighbor || (i === Math.floor(size/2) && j === Math.floor(size/2))) {moves.push([i, j]);}}}}return moves;}// Minimax 算法 + Alpha-Beta 剪枝function minimax(depth, alpha, beta, isMaximizing) {if (depth === 0) return evaluateBoard();let validMoves = getValidMoves();if (validMoves.length === 0) return evaluateBoard();if (isMaximizing) {let maxEval = -Infinity;for (let [row, col] of validMoves) {board[row][col] = 2;if (checkWin(row, col, 2)) {board[row][col] = 0;return Infinity;}let evalScore = minimax(depth - 1, alpha, beta, false);board[row][col] = 0;maxEval = Math.max(maxEval, evalScore);alpha = Math.max(alpha, evalScore);if (beta <= alpha) break;}return maxEval;} else {let minEval = Infinity;for (let [row, col] of validMoves) {board[row][col] = 1;if (checkWin(row, col, 1)) {board[row][col] = 0;return -Infinity;}let evalScore = minimax(depth - 1, alpha, beta, true);board[row][col] = 0;minEval = Math.min(minEval, evalScore);beta = Math.min(beta, evalScore);if (beta <= alpha) break;}return minEval;}}// AI 选择最佳落子function aiMove() {let bestScore = -Infinity;let bestMove = null;const validMoves = getValidMoves();const depth = 2;const startTime = performance.now();for (let [row, col] of validMoves) {board[row][col] = 2;let score = minimax(depth, -Infinity, Infinity, false);board[row][col] = 0;if (score > bestScore) {bestScore = score;bestMove = [row, col];}if (performance.now() - startTime > 4500) break;}if (bestMove) {const [row, col] = bestMove;board[row][col] = 2;moveHistory.push({ player: 2, row, col });lastMove = [row, col];drawBoard();clearInterval(timer);status.textContent = `白棋(AI)落子:(${row}, ${col})`;if (checkWin(row, col, 2)) {status.textContent = `游戏结束!白棋(AI)获胜!`;timerDisplay.textContent = '';gameOver = true;} else {currentPlayer = 1;setTimeout(() => {status.textContent = '当前玩家:黑棋(玩家)';startTimer(20);}, 2000);}} else {clearInterval(timer);status.textContent = '游戏结束!白棋(AI)超时或无合法落子,黑棋(玩家)获胜!';timerDisplay.textContent = '';gameOver = true;}}// 计时器function startTimer(seconds) {timeLeft = seconds;timerDisplay.textContent = `剩余时间:${timeLeft}秒`;clearInterval(timer);timer = setInterval(() => {timeLeft--;timerDisplay.textContent = `剩余时间:${timeLeft}秒`;if (timeLeft <= 0) {clearInterval(timer);if (currentPlayer === 1) {status.textContent = '游戏结束!黑棋(玩家)超时,白棋(AI)获胜!';timerDisplay.textContent = '';gameOver = true;} else {status.textContent = '游戏结束!白棋(AI)超时,黑棋(玩家)获胜!';timerDisplay.textContent = '';gameOver = true;}}}, 1000);}// 处理玩家点击canvas.addEventListener('click', (e) => {if (gameOver || currentPlayer !== 1) return;const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;const row = Math.round((y - offset) / cellSize);const col = Math.round((x - offset) / cellSize);if (row >= 0 && row < size && col >= 0 && col < size && board[row][col] === 0) {board[row][col] = 1;moveHistory.push({ player: 1, row, col });lastMove = [row, col];drawBoard();clearInterval(timer);status.textContent = `黑棋(玩家)落子:(${row}, ${col})`;if (checkWin(row, col, 1)) {status.textContent = `游戏结束!黑棋(玩家)获胜!`;timerDisplay.textContent = '';gameOver = true;} else {currentPlayer = 2;setTimeout(() => {status.textContent = 'AI(白棋)思考中...';timerDisplay.textContent = '剩余时间:5秒';startTimer(5);setTimeout(() => {aiMove();}, 500);}, 2000);}}});// 悔棋undoBtn.addEventListener('click', () => {if (gameOver || undoCount <= 0 || currentPlayer !== 1 || moveHistory.length < 2) return;for (let i = 0; i < 2; i++) {if (moveHistory.length > 0) {const last = moveHistory.pop();board[last.row][last.col] = 0;}}undoCount--;undoBtn.textContent = `悔棋(剩余${undoCount}次)`;if (undoCount === 0) undoBtn.disabled = true;lastMove = moveHistory.length > 0 ? [moveHistory[moveHistory.length - 1].row, moveHistory[moveHistory.length - 1].col] : null;drawBoard();clearInterval(timer);status.textContent = '当前玩家:黑棋(玩家)';startTimer(20);});// 重新开始restartBtn.addEventListener('click', () => {board.forEach(row => row.fill(0));currentPlayer = 1;gameOver = false;undoCount = 3;moveHistory = [];lastMove = null;undoBtn.textContent = `悔棋(剩余${undoCount}次)`;undoBtn.disabled = false;status.textContent = '当前玩家:黑棋(玩家)';timerDisplay.textContent = '剩余时间:20秒';clearInterval(timer);drawBoard();startTimer(20);});// 初始化棋盘并启动计时器drawBoard();startTimer(20);</script>
</body>
</html>

运行结果

优化点

  • 美观性:使用 Tailwind CSS 优化布局,添加渐变背景、木纹棋盘、按钮悬停效果,提升视觉体验。
  • 边缘棋子:棋盘格线和棋子从偏移 30px 开始绘制,canvas 尺寸调整为 480x480,确保边缘棋子完整显示。

http://www.dtcms.com/a/278810.html

相关文章:

  • 【Java EE】多线程-初阶 认识线程(Thread)
  • 【C语言进阶】指针面试题详解(2)
  • 面试 | JS 面试题 整理(更ing)2/34
  • Android 16系统源码_窗口动画(二)窗口显示动画源码调用流程
  • 护照阅读器:国外证件识别的 OCR “解码师”
  • Python 中调用阿里云 OCR(Optical Character Recognition,光学字符识别)服务
  • STM32介绍和GPIO
  • stm32-Modbus主机移植程序理解以及实战
  • argus/nvarguscamerasrc 远程显示报错
  • 项目一第一天
  • 纯数学专业VS应用数学专业:这两个哪个就业面更广?
  • C++后端面试八股文
  • Linux 基础命令详解:从入门到实践(1)
  • JAVA 并发 ThreadLocal
  • RestAssured(Java)使用详解
  • 19.数据增强技术
  • 管程! 解决互斥,同步问题的现代化手段(操作系统os)
  • Java行为型模式---模板方法模式
  • Imx6ull用网线与电脑连接
  • SpringBoot JAR 反编译替换文件
  • 【嵌入式汇编基础】-操作系统基础(三)
  • 【每日刷题】移动零
  • LabVIEW-Origin 船模数据处理系统
  • 【爬虫】Python实现爬取京东商品信息(超详细)
  • 期权和期货的区别主要是什么?
  • [论文阅读] 人工智能 | 用大型语言模型玩转多语言主观性检测:CheckThat! 2025赛事中的亮眼表现
  • Unity3D + VS2022连接雷电模拟器调试
  • 【PTA数据结构 | C语言版】字符串连接操作(不限长)
  • 分布式一致性协议
  • Android动画:属性动画以及实现点击图标缩放的动画效果