五子棋

1 阅读8分钟
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>五子棋 - AI对战</title>
    <link rel="stylesheet" href="css/gomoku.css">
</head>
<body>
    <div class="game-container">
        <h1>五子棋游戏</h1>
        <div class="game-info">
            <div class="status">轮到你下棋 (黑子)</div>
            <button id="restart-btn">重新开始</button>
        </div>
        <div class="board-container">
            <canvas id="game-board" width="600" height="600"></canvas>
        </div>
        <div class="difficulty">
            <label>难度: </label>
            <select id="difficulty-select">
                <option value="1">简单</option>
                <option value="2" selected>中等</option>
                <option value="3">困难</option>
            </select>
        </div>
    </div>
    <script src="js/gomoku_optimized.js"></script>
</body>
</html>
.game-container {
    max-width: 650px;
    margin: 20px auto;
    padding: 30px;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f8f5f0;
    border-radius: 12px;
    box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}

h1 {
    text-align: center;
    color: #8B4513;
    font-family: 'SimSun', serif;
    font-size: 2.2em;
    margin-bottom: 25px;
    text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
    letter-spacing: 2px;
}

.game-info {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
}

.status {
    font-size: 1.2em;
    font-weight: bold;
    color: #555;
}

#restart-btn {
    padding: 10px 20px;
    background-color: #8B4513;
    color: white;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    font-size: 1.1em;
    font-weight: bold;
    transition: all 0.3s ease;
    box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

#restart-btn:hover {
    background-color: #A0522D;
    transform: translateY(-2px);
    box-shadow: 0 6px 12px rgba(0,0,0,0.25);
}

.board-container {
    border: 15px solid #8B4513;
    border-radius: 10px;
    background: linear-gradient(45deg, #DEB887 0%, #D2B48C 50%, #DEB887 100%);
    box-shadow: 0 8px 30px rgba(0,0,0,0.3), inset 0 0 30px rgba(139, 69, 19, 0.2);
    padding: 10px;
    background-image: url('data:image/svg+xml;utf8,<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><filter id="noise"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/></filter><rect width="200" height="200" filter="url(%23noise)" opacity="0.05"/></svg>'), url('data:image/svg+xml;utf8,<svg width="100" height="200" viewBox="0 0 100 200" xmlns="http://www.w3.org/2000/svg"><path d="M0,0 Q50,50 100,0 Q150,50 100,100 Q50,150 100,200 Q150,150 100,100 Q50,50 0,100 Q-50,150 0,200 Q50,150 0,100 Q-50,50 0,0" fill="none" stroke="rgba(139, 69, 19, 0.1)" stroke-width="2"/></svg>');
    background-size: 200px 200px, 100px 200px;
}

#game-board {
    display: block;
    margin: 0 auto;
    background-color: transparent;
    cursor: pointer;
    border-radius: 5px;
    box-shadow: inset 0 0 20px rgba(0,0,0,0.15);
    transition: all 0.3s ease;
}

div.difficulty {
    margin-top: 15px;
    text-align: center;
}

#difficulty-select {
    padding: 8px 12px;
    font-size: 1em;
    margin-left: 10px;
    border: 2px solid #8B4513;
    border-radius: 6px;
    background-color: white;
    color: #333;
    cursor: pointer;
    transition: all 0.3s ease;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

#difficulty-select:hover {
    border-color: #A0522D;
    box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
class GomokuGame {
    // 游戏常量定义
    static get CONSTANTS() {
        return {
            BOARD_SIZE: 15,
            MIN_SIZE: 300,
            STONE_RADIUS_RATIO: 0.45,
            STAR_POINTS: [3, 7, 11],
            ANIMATION_DURATION: 200,
            AI_DELAY: 500,
            SCORES: {
                FIVE_IN_ROW: 100000,
                OPEN_FOUR: 10000,
                冲四: 1000,
                OPEN_THREE: 1000,
                冲三: 100,
                OPEN_TWO: 100,
                冲二: 10,
                OPEN_ONE: 10
            }
        };
    }

    constructor() {
        // DOM元素初始化
        this.canvas = document.getElementById('game-board');
        this.ctx = this.canvas.getContext('2d');
        this.restartBtn = document.getElementById('restart-btn');
        this.statusElement = document.querySelector('.status');
        this.difficultySelect = document.getElementById('difficulty-select');

        // 游戏状态初始化
        this.initGameDimensions();
        this.board = this.createEmptyBoard();
        this.currentPlayer = 1; // 1:玩家(黑), 2:AI(白)
        this.gameActive = true;
        this.difficulty = parseInt(this.difficultySelect.value);
        this.animating = false;
        this.animationRadius = 0;
        this.lastMove = null;
        this.hoverPosition = null;
        this.winningLine = null;

        // 初始化事件监听
        this.initEventListeners();
        // 绘制初始棋盘
        this.drawBoard();
        this.statusElement.textContent = '轮到你下棋 (黑子)';
    }

    // 初始化游戏尺寸
    initGameDimensions() {
        const { BOARD_SIZE, MIN_SIZE } = GomokuGame.CONSTANTS;
        const container = this.canvas.parentElement;
        const containerSize = Math.min(container.clientWidth, container.clientHeight);
        const canvasSize = Math.max(containerSize, MIN_SIZE);

        this.canvas.width = canvasSize;
        this.canvas.height = canvasSize;
        this.canvas.style.width = '100%';
        this.canvas.style.height = 'auto';

        this.cellSize = canvasSize / BOARD_SIZE;
        this.stoneRadius = this.cellSize * GomokuGame.CONSTANTS.STONE_RADIUS_RATIO;
    }

    // 创建空棋盘
    createEmptyBoard() {
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;
        return Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(0));
    }

    // 初始化事件监听
    initEventListeners() {
        this.canvas.addEventListener('click', (e) => this.handleCanvasClick(e));
        this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
        this.canvas.addEventListener('mouseleave', () => this.handleMouseLeave());
        this.restartBtn.addEventListener('click', () => this.restartGame());
        this.difficultySelect.addEventListener('change', (e) => {
            this.difficulty = parseInt(e.target.value);
        });
        // 添加窗口大小调整监听
        window.addEventListener('resize', () => this.handleWindowResize());
    }

    // 处理窗口大小调整
    handleWindowResize() {
        this.initGameDimensions();
        this.drawBoard();
    }

    // 绘制棋盘
    drawBoard() {
        this.clearCanvas();
        this.drawGrid();
        this.drawStarPoints();
        this.drawStones();
        this.drawLastMoveHighlight();
        this.drawWinningLine();
        this.drawHoverPreview();
    }

    // 清空画布
    clearCanvas() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }

    // 绘制网格线
    drawGrid() {
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;
        this.ctx.strokeStyle = '#000';
        this.ctx.lineWidth = 1;

        for (let i = 0; i < BOARD_SIZE; i++) {
            // 横线
            this.ctx.beginPath();
            this.ctx.moveTo(this.cellSize / 2, this.cellSize / 2 + i * this.cellSize);
            this.ctx.lineTo(this.canvas.width - this.cellSize / 2, this.cellSize / 2 + i * this.cellSize);
            this.ctx.stroke();

            // 竖线
            this.ctx.beginPath();
            this.ctx.moveTo(this.cellSize / 2 + i * this.cellSize, this.cellSize / 2);
            this.ctx.lineTo(this.cellSize / 2 + i * this.cellSize, this.canvas.height - this.cellSize / 2);
            this.ctx.stroke();
        }
    }

    // 绘制星位点
    drawStarPoints() {
        const { STAR_POINTS } = GomokuGame.CONSTANTS;
        this.ctx.fillStyle = '#000';

        STAR_POINTS.forEach(x => {
            STAR_POINTS.forEach(y => {
                this.ctx.beginPath();
                this.ctx.arc(
                    this.cellSize / 2 + x * this.cellSize,
                    this.cellSize / 2 + y * this.cellSize,
                    3,
                    0,
                    Math.PI * 2
                );
                this.ctx.fill();
            });
        });
    }

    // 绘制所有棋子
    drawStones() {
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;
        for (let i = 0; i < BOARD_SIZE; i++) {
            for (let j = 0; j < BOARD_SIZE; j++) {
                if (this.board[i][j] !== 0) {
                    this.drawStone(i, j, this.board[i][j]);
                }
            }
        }
    }

    // 绘制单个棋子
    drawStone(x, y, player, animate = false) {
        const centerX = this.cellSize / 2 + x * this.cellSize;
        const centerY = this.cellSize / 2 + y * this.cellSize;
        const radius = Math.max(animate ? this.animationRadius : this.stoneRadius, 0.1);

        // 创建棋子渐变效果
        const gradient = this.ctx.createRadialGradient(
            centerX - 5, centerY - 5, 2,
            centerX, centerY, radius
        );

        // 设置棋子颜色
        if (player === 1) {
            gradient.addColorStop(0, '#666');
            gradient.addColorStop(1, '#000');
        } else {
            gradient.addColorStop(0, '#ddd');
            gradient.addColorStop(1, '#999');
        }

        // 绘制棋子
        this.ctx.beginPath();
        this.ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
        this.ctx.fillStyle = gradient;
        this.ctx.fill();

        // 绘制棋子边框
        this.ctx.strokeStyle = '#000';
        this.ctx.lineWidth = 1;
        this.ctx.stroke();
    }

    // 棋子放置动画
    animateStonePlacement(x, y, player) {
        this.animating = true;
        this.animationRadius = 0;
        const targetRadius = this.stoneRadius;
        const duration = GomokuGame.CONSTANTS.ANIMATION_DURATION;
        const startTime = performance.now();

        const animate = (currentTime) => {
            const elapsed = currentTime - startTime;
            const progress = Math.min(elapsed / duration, 1);
            // 使用缓动函数使动画更自然
            const easeOutQuad = progress * (2 - progress);
            this.animationRadius = targetRadius * easeOutQuad;

            this.drawBoard();
            this.drawStone(x, y, player, true);

            if (progress < 1) {
                requestAnimationFrame(animate);
            } else {
                this.animating = false;
                this.animationRadius = targetRadius;
                this.drawBoard();
            }
        };

        requestAnimationFrame(animate);
    }

    // 处理画布点击事件
    handleCanvasClick(e) {
        if (!this.gameActive || this.currentPlayer !== 1 || this.animating) return;

        const rect = this.canvas.getBoundingClientRect();
        const scaleX = this.canvas.width / rect.width;
        const scaleY = this.canvas.height / rect.height;
        const x = (e.clientX - rect.left) * scaleX;
        const y = (e.clientY - rect.top) * scaleY;

        const gridX = Math.round((x - this.cellSize / 2) / this.cellSize);
        const gridY = Math.round((y - this.cellSize / 2) / this.cellSize);

        if (this.isValidMove(gridX, gridY)) {
            this.makeMove(gridX, gridY, this.currentPlayer);
            this.animateStonePlacement(gridX, gridY, this.currentPlayer);

            if (this.checkGameStatus()) return;

            this.currentPlayer = 2;
            this.statusElement.textContent = 'AI思考中...';

            setTimeout(() => {
                this.aiMakeMove();
                if (!this.checkGameStatus()) {
                    this.currentPlayer = 1;
                    this.statusElement.textContent = '轮到你下棋 (黑子)';
                }
            }, GomokuGame.CONSTANTS.AI_DELAY);
        }
    }

    // 检查落子是否有效
    isValidMove(x, y) {
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;
        return x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && this.board[x][y] === 0;
    }

    // 落子
    makeMove(x, y, player) {
        this.board[x][y] = player;
        this.lastMove = { x, y };
    }

    // 检查游戏状态
    checkGameStatus() {
        if (this.checkWin(this.currentPlayer)) {
            this.gameActive = false;
            const winner = this.currentPlayer === 1 ? '你赢了!' : 'AI赢了!';
            this.statusElement.textContent = winner;
            return true;
        }

        if (this.checkDraw()) {
            this.gameActive = false;
            this.statusElement.textContent = '平局!';
            return true;
        }

        return false;
    }

    // 检查是否获胜并记录获胜连子
    checkWin(player) {
        const directions = [
            [1, 0],  // 水平
            [0, 1],  // 垂直
            [1, 1],  // 对角线
            [1, -1]  // 反对角线
        ];
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;

        for (let i = 0; i < BOARD_SIZE; i++) {
            for (let j = 0; j < BOARD_SIZE; j++) {
                if (this.board[i][j] === player) {
                    for (const [dx, dy] of directions) {
                        const winningLine = this.checkDirection(i, j, dx, dy, player);
                        if (winningLine.length >= 5) {
                            this.winningLine = winningLine;
                            return true;
                        }
                    }
                }
            }
        }
        this.winningLine = null;
        return false;
    }

    // 检查特定方向上的连子
    checkDirection(x, y, dx, dy, player) {
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;
        const line = [{ x, y }];

        // 正方向检查
        for (let k = 1; k < 5; k++) {
            const nx = x + dx * k;
            const ny = y + dy * k;
            if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && this.board[nx][ny] === player) {
                line.push({ x: nx, y: ny });
            } else {
                break;
            }
        }

        // 反方向检查
        for (let k = 1; k < 5; k++) {
            const nx = x - dx * k;
            const ny = y - dy * k;
            if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && this.board[nx][ny] === player) {
                line.unshift({ x: nx, y: ny });
            } else {
                break;
            }
        }

        return line;
    }

    // 检查是否平局
    checkDraw() {
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;
        for (let i = 0; i < BOARD_SIZE; i++) {
            for (let j = 0; j < BOARD_SIZE; j++) {
                if (this.board[i][j] === 0) {
                    return false;
                }
            }
        }
        return true;
    }

    // AI落子
    aiMakeMove() {
        const depth = this.getSearchDepth();
        const move = this.minimax(depth, -Infinity, Infinity, true);

        if (move && this.isValidMove(move.x, move.y)) {
            this.makeMove(move.x, move.y, 2);
            this.animateStonePlacement(move.x, move.y, 2);
            this.checkGameStatus();
        } else if (this.checkDraw()) {
            this.gameActive = false;
            this.statusElement.textContent = '平局!';
        }
    }

    // 根据难度获取搜索深度
    getSearchDepth() {
        switch (this.difficulty) {
            case 1: return 1;
            case 2: return 3;
            case 3: return 4;
            default: return 3;
        }
    }

    // Minimax算法实现AI决策
    minimax(depth, alpha, beta, isMaximizingPlayer) {
        // 终端节点评估
        if (depth === 0 || this.checkWin(1) || this.checkWin(2) || this.checkDraw()) {
            return { score: this.evaluateBoard() };
        }

        const originalBoard = JSON.parse(JSON.stringify(this.board));
        const moves = this.getAvailableMoves();

        if (isMaximizingPlayer) {
            return this.maximizeScore(depth, alpha, beta, moves, originalBoard);
        } else {
            return this.minimizeScore(depth, alpha, beta, moves, originalBoard);
        }
    }

    // 最大化分数(AI回合)
    maximizeScore(depth, alpha, beta, moves, originalBoard) {
        let bestScore = -Infinity;
        let bestMove = null;

        for (const move of moves) {
            this.board[move.x][move.y] = 2;
            const result = this.minimax(depth - 1, alpha, beta, false);
            this.board = JSON.parse(JSON.stringify(originalBoard));

            if (result.score > bestScore) {
                bestScore = result.score;
                bestMove = move;
            }

            alpha = Math.max(alpha, bestScore);
            if (beta <= alpha) break; // Alpha剪枝
        }

        return bestMove ? { ...bestMove, score: bestScore } : { score: bestScore };
    }

    // 最小化分数(玩家回合)
    minimizeScore(depth, alpha, beta, moves, originalBoard) {
        let bestScore = Infinity;

        for (const move of moves) {
            this.board[move.x][move.y] = 1;
            const result = this.minimax(depth - 1, alpha, beta, true);
            this.board[move.x][move.y] = 0; // 回溯

            bestScore = Math.min(bestScore, result.score);
            beta = Math.min(beta, bestScore);
            if (beta <= alpha) break; // Beta剪枝
        }

        return { score: bestScore };
    }

    // 获取所有可用落子位置
    getAvailableMoves() {
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;
        const moves = [];
        const totalStones = this.board.flat().filter(cell => cell !== 0).length;

        // 优化搜索:只考虑有相邻棋子的位置或开局中心区域
        for (let i = 0; i < BOARD_SIZE; i++) {
            for (let j = 0; j < BOARD_SIZE; j++) {
                if (this.board[i][j] === 0 && (
                    totalStones < 5 ||
                    this.hasAdjacentStones(i, j) ||
                    (i > 3 && i < 11 && j > 3 && j < 11) // 中心区域
                )) {
                    moves.push({ x: i, y: j, score: this.evaluateMove(i, j) });
                }
            }
        }

        // 如果没有可用位置,返回中心
        if (moves.length === 0) {
            return [{ x: Math.floor(BOARD_SIZE / 2), y: Math.floor(BOARD_SIZE / 2) }];
        }

        // 按评分排序,优先搜索高分位置(提高剪枝效率)
        return moves.sort((a, b) => b.score - a.score);
    }

    // 检查是否有相邻棋子
    hasAdjacentStones(x, y) {
        for (let dx = -1; dx <= 1; dx++) {
            for (let dy = -1; dy <= 1; dy++) {
                if (dx === 0 && dy === 0) continue;
                const nx = x + dx;
                const ny = y + dy;
                if (this.isOnBoard(nx, ny) && this.board[nx][ny] !== 0) {
                    return true;
                }
            }
        }
        return false;
    }

    // 检查坐标是否在棋盘上
    isOnBoard(x, y) {
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;
        return x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE;
    }

    // 评估单个落子位置的价值
    evaluateMove(x, y) {
        // 临时落子并评估
        this.board[x][y] = 2;
        const aiScore = this.evaluateLine(x, y, 2);
        this.board[x][y] = 1;
        const playerScore = this.evaluateLine(x, y, 1);
        this.board[x][y] = 0; // 回溯

        // AI优先考虑进攻,其次防守
        return aiScore * 1.2 + playerScore;
    }

    // 评估棋盘状态
    evaluateBoard() {
        let aiScore = 0;
        let playerScore = 0;
        const { BOARD_SIZE } = GomokuGame.CONSTANTS;

        // 评估所有位置
        for (let i = 0; i < BOARD_SIZE; i++) {
            for (let j = 0; j < BOARD_SIZE; j++) {
                if (this.board[i][j] === 2) {
                    aiScore += this.evaluateLine(i, j, 2);
                } else if (this.board[i][j] === 1) {
                    playerScore += this.evaluateLine(i, j, 1);
                }
            }
        }

        return aiScore - playerScore;
    }

    // 评估某个位置在各个方向上的连子情况
    evaluateLine(x, y, player) {
        const directions = [
            [1, 0],  // 水平
            [0, 1],  // 垂直
            [1, 1],  // 对角线
            [1, -1]  // 反对角线
        ];
        let totalScore = 0;

        for (const [dx, dy] of directions) {
            const { count, blocked } = this.countInDirection(x, y, dx, dy, player);
            totalScore += this.calculateScore(count, blocked);
        }

        return totalScore;
    }

    // 计算某个方向上的连子数量和阻塞情况
    countInDirection(x, y, dx, dy, player) {
        let count = 1;
        let blocked = 0;

        // 正方向
        for (let k = 1; k < 5; k++) {
            const nx = x + dx * k;
            const ny = y + dy * k;
            const result = this.checkPositionStatus(nx, ny, player);
            if (result === 'same') count++;
            else if (result === 'blocked') { blocked++; break; }
            else break; // 空位
        }

        // 反方向
        for (let k = 1; k < 5; k++) {
            const nx = x - dx * k;
            const ny = y - dy * k;
            const result = this.checkPositionStatus(nx, ny, player);
            if (result === 'same') count++;
            else if (result === 'blocked') { blocked++; break; }
            else break; // 空位
        }

        return { count, blocked };
    }

    // 检查位置状态
    checkPositionStatus(x, y, player) {
        if (!this.isOnBoard(x, y)) return 'blocked';
        if (this.board[x][y] === player) return 'same';
        if (this.board[x][y] === 0) return 'empty';
        return 'blocked';
    }

    // 根据连子数量和阻塞情况计算分数
    calculateScore(count, blocked) {
        const { SCORES } = GomokuGame.CONSTANTS;

        if (count >= 5) return SCORES.FIVE_IN_ROW;
        if (count === 4) return blocked === 0 ? SCORES.OPEN_FOUR : SCORES.冲四;
        if (count === 3) return blocked === 0 ? SCORES.OPEN_THREE : SCORES.冲三;
        if (count === 2) return blocked === 0 ? SCORES.OPEN_TWO : SCORES.冲二;
        if (count === 1) return blocked === 0 ? SCORES.OPEN_ONE : 0;
        return 0;
    }

    // 处理鼠标移动
    handleMouseMove(e) {
        if (!this.gameActive || this.currentPlayer !== 1 || this.animating) {
            this.hoverPosition = null;
            this.drawBoard();
            return;
        }

        const rect = this.canvas.getBoundingClientRect();
        const scaleX = this.canvas.width / rect.width;
        const scaleY = this.canvas.height / rect.height;
        const x = (e.clientX - rect.left) * scaleX;
        const y = (e.clientY - rect.top) * scaleY;

        const gridX = Math.round((x - this.cellSize / 2) / this.cellSize);
        const gridY = Math.round((y - this.cellSize / 2) / this.cellSize);

        this.hoverPosition = this.isValidMove(gridX, gridY) ? { x: gridX, y: gridY } : null;
        this.drawBoard();
    }

    // 处理鼠标离开
    handleMouseLeave() {
        this.hoverPosition = null;
        this.drawBoard();
    }

    // 绘制悬停预览棋子
    drawHoverPreview() {
        if (!this.hoverPosition) return;

        const { x, y } = this.hoverPosition;
        const centerX = this.cellSize / 2 + x * this.cellSize;
        const centerY = this.cellSize / 2 + y * this.cellSize;

        // 绘制半透明预览棋子
        this.ctx.beginPath();
        this.ctx.arc(centerX, centerY, this.stoneRadius, 0, Math.PI * 2);
        this.ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
        this.ctx.fill();
    }

    // 绘制最后一步高亮
    drawLastMoveHighlight() {
        if (!this.lastMove) return;

        const { x, y } = this.lastMove;
        const centerX = this.cellSize / 2 + x * this.cellSize;
        const centerY = this.cellSize / 2 + y * this.cellSize;

        // 绘制高亮圆环
        this.ctx.beginPath();
        this.ctx.arc(centerX, centerY, this.stoneRadius + 4, 0, Math.PI * 2);
        this.ctx.strokeStyle = '#FFD700';
        this.ctx.lineWidth = 3;
        this.ctx.stroke();
    }

    // 绘制获胜连子高亮
    drawWinningLine() {
        if (!this.winningLine || this.winningLine.length < 5) return;

        this.ctx.beginPath();
        // 起始点
        const first = this.winningLine[0];
        const startX = this.cellSize / 2 + first.x * this.cellSize;
        const startY = this.cellSize / 2 + first.y * this.cellSize;
        this.ctx.moveTo(startX, startY);

        // 连接所有获胜棋子
        this.winningLine.forEach(point => {
            const x = this.cellSize / 2 + point.x * this.cellSize;
            const y = this.cellSize / 2 + point.y * this.cellSize;
            this.ctx.lineTo(x, y);
        });

        // 绘制红色高亮线
        this.ctx.strokeStyle = '#FF3333';
        this.ctx.lineWidth = 5;
        this.ctx.lineCap = 'round';
        this.ctx.lineJoin = 'round';
        this.ctx.stroke();
    }

    // 重新开始游戏
    restartGame() {
        this.board = this.createEmptyBoard();
        this.currentPlayer = 1;
        this.gameActive = true;
        this.lastMove = null;
        this.hoverPosition = null;
        this.winningLine = null;
        this.statusElement.textContent = '轮到你下棋 (黑子)';
        this.drawBoard();
    }
}

// 页面加载完成后初始化游戏
window.addEventListener('load', function () {
    const game = new GomokuGame();
});