五子棋 HTML By MiniMax2.5 & Trae 亲测可用
·
<!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>
@import url('https://fonts.googleapis.com/css2?family=Ma+Shan+Zheng&family=Noto+Serif+SC:wght@400;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background:
radial-gradient(ellipse at 20% 80%, rgba(139, 90, 43, 0.15) 0%, transparent 50%),
radial-gradient(ellipse at 80% 20%, rgba(85, 107, 47, 0.12) 0%, transparent 50%),
linear-gradient(180deg, #f5f0e8 0%, #e8e0d5 50%, #d4c8b8 100%);
font-family: 'Noto Serif SC', serif;
overflow: hidden;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%238b7355' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
pointer-events: none;
z-index: -1;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 8px;
}
.title {
font-family: 'Ma Shan Zheng', cursive;
font-size: 3.2rem;
color: #2c1810;
text-shadow:
2px 2px 0 rgba(139, 90, 43, 0.2),
4px 4px 8px rgba(0, 0, 0, 0.15);
letter-spacing: 8px;
position: relative;
}
.title::after {
content: '· 人机对战 ·';
display: block;
font-family: 'Noto Serif SC', serif;
font-size: 0.9rem;
color: #6b5344;
letter-spacing: 4px;
margin-top: 4px;
}
.game-wrapper {
display: flex;
align-items: flex-start;
gap: 32px;
}
.board-container {
position: relative;
padding: 24px;
background:
linear-gradient(135deg, rgba(205, 170, 120, 0.3) 0%, rgba(180, 140, 90, 0.2) 100%),
linear-gradient(to bottom, #d4a574 0%, #c49a6c 50%, #b8895a 100%);
border-radius: 8px;
box-shadow:
0 8px 32px rgba(44, 24, 16, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2),
inset 0 -2px 4px rgba(0, 0, 0, 0.1);
}
.board-container::before {
content: '';
position: absolute;
top: 8px;
left: 8px;
right: 8px;
bottom: 8px;
border: 1px solid rgba(139, 90, 43, 0.3);
border-radius: 4px;
pointer-events: none;
}
#gameCanvas {
display: block;
cursor: pointer;
border-radius: 2px;
}
.side-panel {
display: flex;
flex-direction: column;
gap: 20px;
min-width: 180px;
}
.status-box {
background: linear-gradient(145deg, #faf6f0 0%, #f0e6d8 100%);
border: 2px solid #c4a77d;
border-radius: 8px;
padding: 20px;
text-align: center;
box-shadow:
0 4px 12px rgba(44, 24, 16, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.status-label {
font-size: 0.85rem;
color: #8b7355;
margin-bottom: 8px;
letter-spacing: 2px;
}
.status-text {
font-size: 1.3rem;
font-weight: 700;
color: #2c1810;
min-height: 32px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.status-text.player-turn {
color: #1a1a1a;
}
.status-text.ai-turn {
color: #4a4a4a;
}
.status-text.win {
color: #8b4513;
animation: pulse 1s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.stone-indicator {
width: 20px;
height: 20px;
border-radius: 50%;
display: inline-block;
vertical-align: middle;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
}
.stone-indicator.black {
background: radial-gradient(circle at 35% 35%, #4a4a4a 0%, #1a1a1a 60%, #000 100%);
}
.stone-indicator.white {
background: radial-gradient(circle at 35% 35%, #ffffff 0%, #e0e0e0 40%, #c0c0c0 100%);
}
.button-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.btn {
padding: 14px 24px;
font-family: 'Noto Serif SC', serif;
font-size: 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
letter-spacing: 2px;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s ease;
}
.btn:hover::before {
left: 100%;
}
.btn-restart {
background: linear-gradient(145deg, #8b7355 0%, #6b5344 100%);
color: #faf6f0;
box-shadow:
0 4px 12px rgba(44, 24, 16, 0.25),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
}
.btn-restart:hover {
background: linear-gradient(145deg, #9b8365 0%, #7b6354 100%);
transform: translateY(-2px);
box-shadow:
0 6px 16px rgba(44, 24, 16, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
}
.btn-undo {
background: linear-gradient(145deg, #f0e6d8 0%, #d4c8b8 100%);
color: #5a4a3a;
border: 2px solid #c4a77d;
box-shadow:
0 4px 12px rgba(44, 24, 16, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.btn-undo:hover:not(:disabled) {
background: linear-gradient(145deg, #faf6f0 0%, #e4d8c8 100%);
transform: translateY(-2px);
box-shadow:
0 6px 16px rgba(44, 24, 16, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.btn-undo:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.info-box {
background: linear-gradient(145deg, #faf6f0 0%, #f0e6d8 100%);
border: 2px solid #c4a77d;
border-radius: 8px;
padding: 16px;
box-shadow:
0 4px 12px rgba(44, 24, 16, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.info-title {
font-size: 0.8rem;
color: #8b7355;
margin-bottom: 12px;
letter-spacing: 2px;
border-bottom: 1px solid #d4c8b8;
padding-bottom: 8px;
}
.move-count {
font-size: 0.9rem;
color: #5a4a3a;
}
.thinking-indicator {
display: none;
align-items: center;
gap: 8px;
font-size: 0.85rem;
color: #8b7355;
}
.thinking-indicator.active {
display: flex;
}
.thinking-dots {
display: flex;
gap: 3px;
}
.thinking-dots span {
width: 6px;
height: 6px;
background: #8b7355;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
}
.thinking-dots span:nth-child(1) { animation-delay: -0.32s; }
.thinking-dots span:nth-child(2) { animation-delay: -0.16s; }
.thinking-dots span:nth-child(3) { animation-delay: 0s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
.score-display {
font-size: 0.8rem;
color: #6b5344;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #d4c8b8;
}
.difficulty-select {
margin-top: 12px;
padding-top: 8px;
border-top: 1px solid #d4c8b8;
display: flex;
align-items: center;
gap: 8px;
}
.difficulty-select label {
font-size: 0.8rem;
color: #6b5344;
}
.difficulty-select select {
flex: 1;
padding: 6px 10px;
font-family: 'Noto Serif SC', serif;
font-size: 0.85rem;
color: #2c1810;
background: linear-gradient(to bottom, #faf6f0 0%, #f0e6d8 100%);
border: 2px solid #c4a77d;
border-radius: 4px;
cursor: pointer;
outline: none;
transition: all 0.2s ease;
}
.difficulty-select select:hover {
border-color: #8b7355;
box-shadow: 0 2px 4px rgba(44, 24, 16, 0.1);
}
.difficulty-select select:focus {
border-color: #6b5344;
box-shadow: 0 2px 6px rgba(44, 24, 16, 0.15);
}
@media (max-width: 768px) {
.game-wrapper {
flex-direction: column;
align-items: center;
}
.side-panel {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
min-width: auto;
}
.button-group {
flex-direction: row;
}
.title {
font-size: 2.4rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 class="title">墨韵五子棋</h1>
</div>
<div class="game-wrapper">
<div class="board-container">
<canvas id="gameCanvas" width="540" height="540"></canvas>
</div>
<div class="side-panel">
<div class="status-box">
<div class="status-label">当前状态</div>
<div class="status-text player-turn" id="statusText">
<span class="stone-indicator black"></span>
您的回合
</div>
<div class="thinking-indicator" id="thinkingIndicator">
<div class="thinking-dots">
<span></span><span></span><span></span>
</div>
AI 思考中...
</div>
</div>
<div class="button-group">
<button class="btn btn-restart" id="btnRestart">重新开始</button>
<button class="btn btn-undo" id="btnUndo" disabled>悔 棋</button>
</div>
<div class="info-box">
<div class="info-title">对局信息</div>
<div class="move-count">步数: <span id="moveCount">0</span></div>
<div class="difficulty-select">
<label for="difficultySelect">AI 难度:</label>
<select id="difficultySelect">
<option value="easy">入门</option>
<option value="medium" selected>进阶</option>
<option value="hard">大师</option>
</select>
</div>
</div>
</div>
</div>
</div>
<script>
const BOARD_SIZE = 15;
const CELL_SIZE = 36;
const PADDING = 18;
const EMPTY = 0;
const BLACK = 1;
const WHITE = 2;
const SCORES = {
LIVE_FOUR: 100000,
FOUR: 10000,
LIVE_THREE: 1000,
SLEEP_THREE: 100,
LIVE_TWO: 10,
SLEEP_TWO: 1
};
const DIRECTIONS = [
[1, 0],
[0, 1],
[1, 1],
[1, -1]
];
let board = [];
let gameStatus = 'playing';
let currentPlayer = BLACK;
let moveHistory = [];
let animationQueue = [];
const DIFFICULTY_CONFIG = {
easy: { depth: 0, candidateCount: 3 },
medium: { depth: 2, candidateCount: 6 },
hard: { depth: 4, candidateCount: 8 }
};
let currentDifficulty = 'medium';
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const statusText = document.getElementById('statusText');
const moveCountEl = document.getElementById('moveCount');
const btnRestart = document.getElementById('btnRestart');
const btnUndo = document.getElementById('btnUndo');
const thinkingIndicator = document.getElementById('thinkingIndicator');
const difficultySelect = document.getElementById('difficultySelect');
function initBoard() {
board = [];
for (let i = 0; i < BOARD_SIZE; i++) {
board[i] = [];
for (let j = 0; j < BOARD_SIZE; j++) {
board[i][j] = EMPTY;
}
}
}
function drawBoard() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#deb887';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#4a3728';
ctx.lineWidth = 1.2;
for (let i = 0; i < BOARD_SIZE; i++) {
ctx.beginPath();
ctx.moveTo(PADDING, PADDING + i * CELL_SIZE);
ctx.lineTo(PADDING + (BOARD_SIZE - 1) * CELL_SIZE, PADDING + i * CELL_SIZE);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(PADDING + i * CELL_SIZE, PADDING);
ctx.lineTo(PADDING + i * CELL_SIZE, PADDING + (BOARD_SIZE - 1) * CELL_SIZE);
ctx.stroke();
}
const starPoints = [
[3, 3], [11, 3], [7, 7],
[3, 11], [11, 11]
];
ctx.fillStyle = '#2c1810';
for (const [x, y] of starPoints) {
ctx.beginPath();
ctx.arc(PADDING + x * CELL_SIZE, PADDING + y * CELL_SIZE, 4, 0, Math.PI * 2);
ctx.fill();
}
}
function drawPiece(x, y, color, animated = false) {
const px = PADDING + x * CELL_SIZE;
const py = PADDING + y * CELL_SIZE;
if (animated) {
animationQueue.push({ x, y, color, progress: 0 });
return;
}
drawPieceImmediate(px, py, color);
}
function drawPieceImmediate(px, py, color) {
ctx.shadowColor = 'rgba(0, 0, 0, 0.4)';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
const gradient = ctx.createRadialGradient(
px - 4, py - 4, 0,
px, py, CELL_SIZE / 2 - 2
);
if (color === BLACK) {
gradient.addColorStop(0, '#5a5a5a');
gradient.addColorStop(0.3, '#3a3a3a');
gradient.addColorStop(1, '#1a1a1a');
} else {
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(0.3, '#f0f0f0');
gradient.addColorStop(1, '#d0d0d0');
}
ctx.beginPath();
ctx.arc(px, py, CELL_SIZE / 2 - 3, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
function animatePieces() {
if (animationQueue.length === 0) return;
const animation = animationQueue[0];
animation.progress += 0.15;
if (animation.progress >= 1) {
drawPieceImmediate(
PADDING + animation.x * CELL_SIZE,
PADDING + animation.y * CELL_SIZE,
animation.color
);
animationQueue.shift();
if (animationQueue.length > 0) {
requestAnimationFrame(animatePieces);
}
return;
}
drawBoard();
drawAllPieces();
const px = PADDING + animation.x * CELL_SIZE;
const py = PADDING + animation.y * CELL_SIZE;
const scale = easeOutBack(animation.progress);
ctx.save();
ctx.translate(px, py);
ctx.scale(scale, scale);
ctx.translate(-px, -py);
drawPieceImmediate(px, py, animation.color);
ctx.restore();
requestAnimationFrame(animatePieces);
}
function easeOutBack(x) {
const c1 = 1.70158;
const c3 = c1 + 1;
return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
}
function drawAllPieces() {
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] !== EMPTY) {
drawPieceImmediate(
PADDING + i * CELL_SIZE,
PADDING + j * CELL_SIZE,
board[i][j]
);
}
}
}
}
function getBoardPosition(clientX, clientY) {
const rect = canvas.getBoundingClientRect();
const x = Math.round((clientX - rect.left - PADDING) / CELL_SIZE);
const y = Math.round((clientY - rect.top - PADDING) / CELL_SIZE);
if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE) {
return { x, y };
}
return null;
}
function checkWin(x, y, color) {
for (const [dx, dy] of DIRECTIONS) {
let count = 1;
for (let i = 1; i < 5; i++) {
const nx = x + dx * i;
const ny = y + dy * i;
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] === color) {
count++;
} else {
break;
}
}
for (let i = 1; i < 5; i++) {
const nx = x - dx * i;
const ny = y - dy * i;
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] === color) {
count++;
} else {
break;
}
}
if (count >= 5) {
return true;
}
}
return false;
}
function isBoardFull() {
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] === EMPTY) {
return false;
}
}
}
return true;
}
function countPattern(line, player) {
const opponent = player === BLACK ? WHITE : BLACK;
let count = 0;
let openEnds = 0;
let temp = '';
for (const cell of line) {
if (cell === player) {
temp += '1';
} else if (cell === opponent) {
temp += '2';
} else {
temp += '0';
}
}
const regex = /1{5}/g;
if (regex.test(temp)) {
return { type: 'FIVE', score: SCORES.LIVE_FOUR * 10 };
}
const liveFourRegex = /011110|011110/;
if (liveFourRegex.test(temp)) {
return { type: 'LIVE_FOUR', score: SCORES.LIVE_FOUR };
}
const fourRegex1 = /211110|011112|11011|101101|011011/;
const fourRegex2 = /11110|01111|10111|11011|11101/;
if (fourRegex1.test(temp) || fourRegex2.test(temp)) {
return { type: 'FOUR', score: SCORES.FOUR };
}
const liveThreeRegex = /011100|001110|011010|010110|11100|00111/;
if (liveThreeRegex.test(temp)) {
return { type: 'LIVE_THREE', score: SCORES.LIVE_THREE };
}
const sleepThreeRegex = /211100|001112|010100|001010|100110|011001|110100|001101|101100|011002/;
if (sleepThreeRegex.test(temp)) {
return { type: 'SLEEP_THREE', score: SCORES.SLEEP_THREE };
}
const liveTwoRegex = /001100|011000|000110|010010|010100|001010/;
if (liveTwoRegex.test(temp)) {
return { type: 'LIVE_TWO', score: SCORES.LIVE_TWO };
}
const sleepTwoRegex = /001000|000100|000010|100010|010001|100100|001001|010100|001010/;
if (sleepTwoRegex.test(temp)) {
return { type: 'SLEEP_TWO', score: SCORES.SLEEP_TWO };
}
return { type: 'NONE', score: 0 };
}
function getLine(x, y, dx, dy, length = 11) {
const line = [];
const startX = x - 5 * dx;
const startY = y - 5 * dy;
for (let i = 0; i < length; i++) {
const nx = startX + i * dx;
const ny = startY + i * dy;
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE) {
line.push(board[nx][ny]);
} else {
line.push(-1);
}
}
return line;
}
function evaluatePosition(x, y, player) {
if (board[x][y] !== EMPTY) return 0;
let totalScore = 0;
for (const [dx, dy] of DIRECTIONS) {
const line = getLine(x, y, dx, dy);
const result = countPattern(line, player);
totalScore += result.score;
}
const centerX = Math.abs(x - 7);
const centerY = Math.abs(y - 7);
const distanceFromCenter = centerX + centerY;
const centerBonus = Math.max(0, 10 - distanceFromCenter) * 0.5;
totalScore += centerBonus;
return totalScore;
}
function evaluateBoard(player) {
let totalScore = 0;
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] === EMPTY) {
totalScore += evaluatePosition(i, j, player);
}
}
}
return totalScore;
}
function getAllEmptyPositions() {
const positions = [];
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] === EMPTY) {
const score = evaluatePosition(i, j, WHITE) + evaluatePosition(i, j, BLACK) * 0.9;
positions.push({ x: i, y: j, score });
}
}
}
positions.sort((a, b) => b.score - a.score);
return positions.slice(0, 15);
}
function minimax(depth, alpha, beta, isMaximizing, player) {
if (depth === 0) {
return evaluateBoard(WHITE) - evaluateBoard(BLACK);
}
const moves = getAllEmptyPositions();
if (moves.length === 0) {
return evaluateBoard(WHITE) - evaluateBoard(BLACK);
}
let bestScore = isMaximizing ? -Infinity : Infinity;
for (const move of moves) {
board[move.x][move.y] = player;
const winMove = checkWin(move.x, move.y, player);
if (winMove) {
board[move.x][move.y] = EMPTY;
return isMaximizing ? 1000000 + depth : -1000000 - depth;
}
const score = minimax(
depth - 1,
alpha,
beta,
!isMaximizing,
player === WHITE ? BLACK : WHITE
);
board[move.x][move.y] = EMPTY;
if (isMaximizing) {
bestScore = Math.max(bestScore, score);
alpha = Math.max(alpha, score);
if (beta <= alpha) break;
} else {
bestScore = Math.min(bestScore, score);
beta = Math.min(beta, score);
if (beta <= alpha) break;
}
}
return bestScore;
}
function aiMove() {
thinkingIndicator.classList.add('active');
setTimeout(() => {
const moves = getAllEmptyPositions();
if (moves.length === 0) {
gameStatus = 'draw';
updateStatus('平局!');
thinkingIndicator.classList.remove('active');
return;
}
const config = DIFFICULTY_CONFIG[currentDifficulty];
let bestMove;
if (config.depth === 0) {
const topMoves = moves.slice(0, config.candidateCount);
bestMove = topMoves[Math.floor(Math.random() * topMoves.length)];
} else {
bestMove = moves[0];
let bestScore = -Infinity;
const alpha = -Infinity;
const beta = Infinity;
const startDepth = Math.min(config.depth, moves.length);
for (const move of moves.slice(0, config.candidateCount)) {
board[move.x][move.y] = WHITE;
if (checkWin(move.x, move.y, WHITE)) {
board[move.x][move.y] = EMPTY;
bestMove = move;
break;
}
const score = minimax(
startDepth - 1,
alpha,
beta,
false,
BLACK
);
board[move.x][move.y] = EMPTY;
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
}
}
makeMove(bestMove.x, bestMove.y, WHITE);
thinkingIndicator.classList.remove('active');
}, 100);
}
function makeMove(x, y, player) {
if (gameStatus !== 'playing') return;
if (board[x][y] !== EMPTY) return;
board[x][y] = player;
moveHistory.push({ x, y, player });
drawBoard();
drawAllPieces();
animatePieces();
moveCountEl.textContent = moveHistory.length;
btnUndo.disabled = false;
if (checkWin(x, y, player)) {
gameStatus = player === BLACK ? 'black_win' : 'white_win';
updateStatus(player === BLACK ? '恭喜,您赢了!' : 'AI 获胜!', 'win');
return;
}
if (isBoardFull()) {
gameStatus = 'draw';
updateStatus('平局!', 'win');
return;
}
currentPlayer = currentPlayer === BLACK ? WHITE : BLACK;
if (currentPlayer === WHITE) {
updateStatus('AI 思考中...', 'ai-turn');
aiMove();
} else {
updateStatus('您的回合', 'player-turn');
}
}
function updateStatus(text, className = '') {
statusText.className = 'status-text ' + className;
if (className === 'player-turn') {
statusText.innerHTML = '<span class="stone-indicator black"></span> 您的回合';
} else if (className === 'ai-turn') {
statusText.innerHTML = '<span class="stone-indicator white"></span> AI 思考中...';
} else if (className === 'win') {
statusText.textContent = text;
} else {
statusText.textContent = text;
}
}
function restartGame() {
initBoard();
gameStatus = 'playing';
currentPlayer = BLACK;
moveHistory = [];
animationQueue = [];
drawBoard();
updateStatus('您的回合', 'player-turn');
moveCountEl.textContent = '0';
btnUndo.disabled = true;
thinkingIndicator.classList.remove('active');
}
function undoMove() {
if (moveHistory.length < 2) return;
let lastMove = moveHistory.pop();
board[lastMove.x][lastMove.y] = EMPTY;
lastMove = moveHistory.pop();
board[lastMove.x][lastMove.y] = EMPTY;
gameStatus = 'playing';
currentPlayer = BLACK;
drawBoard();
drawAllPieces();
updateStatus('您的回合', 'player-turn');
moveCountEl.textContent = moveHistory.length;
btnUndo.disabled = moveHistory.length < 2;
thinkingIndicator.classList.remove('active');
}
canvas.addEventListener('click', (e) => {
if (gameStatus !== 'playing') return;
if (currentPlayer !== BLACK) return;
const pos = getBoardPosition(e.clientX, e.clientY);
if (pos && board[pos.x][pos.y] === EMPTY) {
makeMove(pos.x, pos.y, BLACK);
}
});
btnRestart.addEventListener('click', restartGame);
btnUndo.addEventListener('click', undoMove);
difficultySelect.addEventListener('change', (e) => {
currentDifficulty = e.target.value;
});
initBoard();
drawBoard();
</script>
</body>
</html>
更多推荐



所有评论(0)