<!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 {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .container {
            background: white;
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
            max-width: 800px;
            width: 100%;
        }

        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 20px;
            font-size: 2.5em;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
        }

        .game-info {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            padding: 10px;
            background: #f5f5f5;
            border-radius: 10px;
        }

        .current-player {
            font-size: 1.2em;
            font-weight: bold;
        }

        .controls {
            display: flex;
            gap: 10px;
        }

        button {
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 1em;
            transition: all 0.3s ease;
        }

        .new-game-btn {
            background: #4CAF50;
            color: white;
        }

        .new-game-btn:hover {
            background: #45a049;
        }

        .difficulty-select {
            padding: 8px;
            border-radius: 5px;
            border: 1px solid #ddd;
        }

        .board-container {
            display: flex;
            justify-content: center;
            margin: 20px 0;
        }

        .board {
            background: #deb887;
            border: 3px solid #8b4513;
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.2);
        }

        .board-grid {
            display: grid;
            grid-template-columns: repeat(15, 30px);
            grid-template-rows: repeat(15, 30px);
            gap: 1px;
            background: #8b4513;
            padding: 1px;
        }

        .cell {
            width: 30px;
            height: 30px;
            background: #deb887;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            transition: background-color 0.2s ease;
        }

        .cell:hover {
            background: #d2b48c;
        }

        .cell.occupied {
            cursor: not-allowed;
        }

        .cell.occupied:hover {
            background: #deb887;
        }

        .stone {
            width: 26px;
            height: 26px;
            border-radius: 50%;
            border: 2px solid #333;
            position: absolute;
            animation: placeStone 0.3s ease;
        }

        @keyframes placeStone {
            0% {
                transform: scale(0);
                opacity: 0;
            }
            100% {
                transform: scale(1);
                opacity: 1;
            }
        }

        .black-stone {
            background: radial-gradient(circle at 30% 30%, #666, #000);
        }

        .white-stone {
            background: radial-gradient(circle at 30% 30%, #fff, #ccc);
        }

        .winning-stone {
            animation: winningPulse 1s infinite;
        }

        @keyframes winningPulse {
            0%, 100% {
                transform: scale(1);
                box-shadow: 0 0 10px rgba(255, 215, 0, 0.8);
            }
            50% {
                transform: scale(1.1);
                box-shadow: 0 0 20px rgba(255, 215, 0, 1);
            }
        }

        .game-status {
            text-align: center;
            font-size: 1.5em;
            font-weight: bold;
            margin-top: 20px;
            min-height: 50px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .winner {
            color: #ff6b6b;
            animation: celebrate 2s ease-in-out;
        }

        @keyframes celebrate {
            0%, 100% {
                transform: scale(1);
            }
            50% {
                transform: scale(1.1);
            }
        }

        .thinking {
            color: #4285f4;
        }

        .instructions {
            background: #f0f8ff;
            padding: 15px;
            border-radius: 10px;
            margin-top: 20px;
            border-left: 4px solid #4285f4;
        }

        .instructions h3 {
            margin-top: 0;
            color: #333;
        }

        .instructions ul {
            margin: 10px 0;
            padding-left: 20px;
        }

        .instructions li {
            margin: 5px 0;
        }

        @media (max-width: 600px) {
            .board-grid {
                grid-template-columns: repeat(15, 25px);
                grid-template-rows: repeat(15, 25px);
            }
            
            .cell {
                width: 25px;
                height: 25px;
            }
            
            .stone {
                width: 21px;
                height: 21px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🤖 人工智能五子棋</h1>
        
        <div class="game-info">
            <div class="current-player">
                当前玩家: <span id="current-player">黑棋 (玩家)</span>
            </div>
            <div class="controls">
                <select id="difficulty" class="difficulty-select">
                    <option value="easy">简单</option>
                    <option value="medium" selected>中等</option>
                    <option value="hard">困难</option>
                </select>
                <button class="new-game-btn" onclick="newGame()">新游戏</button>
            </div>
        </div>

        <div class="board-container">
            <div class="board">
                <div id="board" class="board-grid"></div>
            </div>
        </div>

        <div id="game-status" class="game-status">
            点击棋盘开始游戏
        </div>

        <div class="instructions">
            <h3>游戏说明:</h3>
            <ul>
                <li>目标: 率先在横、竖、斜任一方向连成五子获胜</li>
                <li>玩家执黑棋先手,AI执白棋后手</li>
                <li>可选择AI难度: 简单、中等、困难</li>
                <li>点击棋盘格子下棋,AI会自动应对</li>
            </ul>
        </div>
    </div>

    <script>
        class GomokuAI {
            constructor() {
                this.BOARD_SIZE = 15;
                this.EMPTY = 0;
                this.BLACK = 1;  // 玩家
                this.WHITE = 2;  // AI
                
                this.board = [];
                this.currentPlayer = this.BLACK;
                this.gameOver = false;
                this.difficulty = 'medium';
                this.lastMove = null;
                
                this.initBoard();
                this.createBoardUI();
                
                // 评分权重
                this.SCORES = {
                    FIVE: 100000,
                    FOUR: 10000,
                    BLOCKED_FOUR: 1000,
                    THREE: 1000,
                    BLOCKED_THREE: 100,
                    TWO: 100,
                    BLOCKED_TWO: 10,
                    ONE: 10
                };
            }

            initBoard() {
                this.board = [];
                for (let i = 0; i < this.BOARD_SIZE; i++) {
                    this.board[i] = [];
                    for (let j = 0; j < this.BOARD_SIZE; j++) {
                        this.board[i][j] = this.EMPTY;
                    }
                }
                this.currentPlayer = this.BLACK;
                this.gameOver = false;
                this.lastMove = null;
                this.updateGameStatus('点击棋盘开始游戏');
            }

            createBoardUI() {
                const boardElement = document.getElementById('board');
                boardElement.innerHTML = '';
                
                for (let i = 0; i < this.BOARD_SIZE; i++) {
                    for (let j = 0; j < this.BOARD_SIZE; j++) {
                        const cell = document.createElement('div');
                        cell.className = 'cell';
                        cell.dataset.row = i;
                        cell.dataset.col = j;
                        cell.addEventListener('click', () => this.handleCellClick(i, j));
                        boardElement.appendChild(cell);
                    }
                }
            }

            handleCellClick(row, col) {
                if (this.gameOver || this.board[row][col] !== this.EMPTY || this.currentPlayer !== this.BLACK) {
                    return;
                }

                this.makeMove(row, col, this.BLACK);
                
                if (this.checkWin(row, col, this.BLACK)) {
                    this.gameOver = true;
                    this.updateGameStatus('🎉 恭喜!玩家获胜!', 'winner');
                    this.highlightWinningStones(row, col, this.BLACK);
                    return;
                }

                if (this.isBoardFull()) {
                    this.gameOver = true;
                    this.updateGameStatus('😐 平局!', 'winner');
                    return;
                }

                this.currentPlayer = this.WHITE;
                this.updateCurrentPlayer();
                this.updateGameStatus('🤖 AI思考中...', 'thinking');

                // AI延迟移动,增加真实感
                setTimeout(() => {
                    this.aiMove();
                }, 500 + Math.random() * 1000);
            }

            makeMove(row, col, player) {
                this.board[row][col] = player;
                this.lastMove = { row, col };
                this.updateCell(row, col, player);
            }

            updateCell(row, col, player) {
                const cellIndex = row * this.BOARD_SIZE + col;
                const cell = document.getElementById('board').children[cellIndex];
                cell.classList.add('occupied');
                
                const stone = document.createElement('div');
                stone.className = `stone ${player === this.BLACK ? 'black-stone' : 'white-stone'}`;
                cell.appendChild(stone);
            }

            aiMove() {
                if (this.gameOver) return;

                const move = this.getBestMove();
                if (move) {
                    this.makeMove(move.row, move.col, this.WHITE);
                    
                    if (this.checkWin(move.row, move.col, this.WHITE)) {
                        this.gameOver = true;
                        this.updateGameStatus('😤 AI获胜!再来一局?', 'winner');
                        this.highlightWinningStones(move.row, move.col, this.WHITE);
                        return;
                    }

                    if (this.isBoardFull()) {
                        this.gameOver = true;
                        this.updateGameStatus('😐 平局!', 'winner');
                        return;
                    }

                    this.currentPlayer = this.BLACK;
                    this.updateCurrentPlayer();
                    this.updateGameStatus('轮到你了!');
                }
            }

            getBestMove() {
                const difficultySettings = {
                    easy: { depth: 2, randomness: 0.3 },
                    medium: { depth: 3, randomness: 0.1 },
                    hard: { depth: 4, randomness: 0.05 }
                };

                const setting = difficultySettings[this.difficulty];
                const moves = this.getValidMoves();
                
                if (moves.length === 0) return null;

                // 如果是第一步,随机选择中心附近位置
                if (moves.length === this.BOARD_SIZE * this.BOARD_SIZE) {
                    const center = Math.floor(this.BOARD_SIZE / 2);
                    const firstMoves = [
                        { row: center, col: center },
                        { row: center - 1, col: center },
                        { row: center + 1, col: center },
                        { row: center, col: center - 1 },
                        { row: center, col: center + 1 }
                    ];
                    return firstMoves[Math.floor(Math.random() * firstMoves.length)];
                }

                // 优先检查关键位置
                const criticalMove = this.findCriticalMove();
                if (criticalMove) return criticalMove;

                // 使用minimax算法
                let bestScore = -Infinity;
                let bestMoves = [];

                // 只考虑有潜力的位置
                const candidateMoves = this.getCandidateMoves();

                for (const move of candidateMoves) {
                    this.board[move.row][move.col] = this.WHITE;
                    const score = this.minimax(setting.depth - 1, -Infinity, Infinity, false);
                    this.board[move.row][move.col] = this.EMPTY;

                    if (score > bestScore) {
                        bestScore = score;
                        bestMoves = [move];
                    } else if (score === bestScore) {
                        bestMoves.push(move);
                    }
                }

                // 添加随机性
                if (Math.random() < setting.randomness && bestMoves.length > 1) {
                    return bestMoves[Math.floor(Math.random() * bestMoves.length)];
                }

                return bestMoves.length > 0 ? bestMoves[0] : moves[0];
            }

            findCriticalMove() {
                // 检查AI能否立即获胜
                for (let i = 0; i < this.BOARD_SIZE; i++) {
                    for (let j = 0; j < this.BOARD_SIZE; j++) {
                        if (this.board[i][j] === this.EMPTY) {
                            this.board[i][j] = this.WHITE;
                            if (this.checkWin(i, j, this.WHITE)) {
                                this.board[i][j] = this.EMPTY;
                                return { row: i, col: j };
                            }
                            this.board[i][j] = this.EMPTY;
                        }
                    }
                }

                // 检查是否需要阻止玩家获胜
                for (let i = 0; i < this.BOARD_SIZE; i++) {
                    for (let j = 0; j < this.BOARD_SIZE; j++) {
                        if (this.board[i][j] === this.EMPTY) {
                            this.board[i][j] = this.BLACK;
                            if (this.checkWin(i, j, this.BLACK)) {
                                this.board[i][j] = this.EMPTY;
                                return { row: i, col: j };
                            }
                            this.board[i][j] = this.EMPTY;
                        }
                    }
                }

                return null;
            }

            getCandidateMoves() {
                const moves = [];
                const visited = new Set();

                // 从已有棋子周围2格内搜索
                for (let i = 0; i < this.BOARD_SIZE; i++) {
                    for (let j = 0; j < this.BOARD_SIZE; j++) {
                        if (this.board[i][j] !== this.EMPTY) {
                            for (let di = -2; di <= 2; di++) {
                                for (let dj = -2; dj <= 2; dj++) {
                                    const ni = i + di;
                                    const nj = j + dj;
                                    const key = `${ni}-${nj}`;
                                    
                                    if (ni >= 0 && ni < this.BOARD_SIZE && 
                                        nj >= 0 && nj < this.BOARD_SIZE && 
                                        this.board[ni][nj] === this.EMPTY && 
                                        !visited.has(key)) {
                                        moves.push({ row: ni, col: nj });
                                        visited.add(key);
                                    }
                                }
                            }
                        }
                    }
                }

                return moves.length > 0 ? moves : this.getValidMoves().slice(0, 50);
            }

            minimax(depth, alpha, beta, isMaximizing) {
                if (depth === 0) {
                    return this.evaluateBoard();
                }

                const moves = this.getCandidateMoves().slice(0, 20); // 限制搜索范围

                if (isMaximizing) {
                    let maxScore = -Infinity;
                    for (const move of moves) {
                        this.board[move.row][move.col] = this.WHITE;
                        const score = this.minimax(depth - 1, alpha, beta, false);
                        this.board[move.row][move.col] = this.EMPTY;
                        
                        maxScore = Math.max(score, maxScore);
                        alpha = Math.max(alpha, score);
                        if (beta <= alpha) break;
                    }
                    return maxScore;
                } else {
                    let minScore = Infinity;
                    for (const move of moves) {
                        this.board[move.row][move.col] = this.BLACK;
                        const score = this.minimax(depth - 1, alpha, beta, true);
                        this.board[move.row][move.col] = this.EMPTY;
                        
                        minScore = Math.min(score, minScore);
                        beta = Math.min(beta, score);
                        if (beta <= alpha) break;
                    }
                    return minScore;
                }
            }

            evaluateBoard() {
                return this.evaluatePlayer(this.WHITE) - this.evaluatePlayer(this.BLACK);
            }

            evaluatePlayer(player) {
                let score = 0;
                
                // 检查所有方向
                const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];
                
                for (let i = 0; i < this.BOARD_SIZE; i++) {
                    for (let j = 0; j < this.BOARD_SIZE; j++) {
                        if (this.board[i][j] === player) {
                            for (const [dx, dy] of directions) {
                                score += this.evaluateLine(i, j, dx, dy, player);
                            }
                        }
                    }
                }
                
                return score;
            }

            evaluateLine(row, col, dx, dy, player) {
                let count = 1;
                let blocked = 0;
                
                // 向前检查
                let r = row + dx, c = col + dy;
                while (r >= 0 && r < this.BOARD_SIZE && c >= 0 && c < this.BOARD_SIZE) {
                    if (this.board[r][c] === player) {
                        count++;
                    } else if (this.board[r][c] === this.EMPTY) {
                        break;
                    } else {
                        blocked++;
                        break;
                    }
                    r += dx;
                    c += dy;
                }
                
                // 向后检查
                r = row - dx;
                c = col - dy;
                while (r >= 0 && r < this.BOARD_SIZE && c >= 0 && c < this.BOARD_SIZE) {
                    if (this.board[r][c] === player) {
                        count++;
                    } else if (this.board[r][c] === this.EMPTY) {
                        break;
                    } else {
                        blocked++;
                        break;
                    }
                    r -= dx;
                    c -= dy;
                }
                
                return this.getScore(count, blocked);
            }

            getScore(count, blocked) {
                if (blocked === 2) return 0;
                
                switch (count) {
                    case 5: return this.SCORES.FIVE;
                    case 4: return blocked === 0 ? this.SCORES.FOUR : this.SCORES.BLOCKED_FOUR;
                    case 3: return blocked === 0 ? this.SCORES.THREE : this.SCORES.BLOCKED_THREE;
                    case 2: return blocked === 0 ? this.SCORES.TWO : this.SCORES.BLOCKED_TWO;
                    case 1: return this.SCORES.ONE;
                    default: return 0;
                }
            }

            checkWin(row, col, player) {
                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 newRow = row + dx * i;
                        const newCol = col + dy * i;
                        if (newRow < 0 || newRow >= this.BOARD_SIZE || 
                            newCol < 0 || newCol >= this.BOARD_SIZE || 
                            this.board[newRow][newCol] !== player) {
                            break;
                        }
                        count++;
                    }
                    
                    // 检查负方向
                    for (let i = 1; i < 5; i++) {
                        const newRow = row - dx * i;
                        const newCol = col - dy * i;
                        if (newRow < 0 || newRow >= this.BOARD_SIZE || 
                            newCol < 0 || newCol >= this.BOARD_SIZE || 
                            this.board[newRow][newCol] !== player) {
                            break;
                        }
                        count++;
                    }
                    
                    if (count >= 5) {
                        this.winDirection = { dx, dy };
                        return true;
                    }
                }
                
                return false;
            }

            highlightWinningStones(row, col, player) {
                const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];
                
                for (const [dx, dy] of directions) {
                    const stones = [{ row, col }];
                    
                    // 收集正方向的棋子
                    for (let i = 1; i < 5; i++) {
                        const newRow = row + dx * i;
                        const newCol = col + dy * i;
                        if (newRow < 0 || newRow >= this.BOARD_SIZE || 
                            newCol < 0 || newCol >= this.BOARD_SIZE || 
                            this.board[newRow][newCol] !== player) {
                            break;
                        }
                        stones.push({ row: newRow, col: newCol });
                    }
                    
                    // 收集负方向的棋子
                    for (let i = 1; i < 5; i++) {
                        const newRow = row - dx * i;
                        const newCol = col - dy * i;
                        if (newRow < 0 || newRow >= this.BOARD_SIZE || 
                            newCol < 0 || newCol >= this.BOARD_SIZE || 
                            this.board[newRow][newCol] !== player) {
                            break;
                        }
                        stones.push({ row: newRow, col: newCol });
                    }
                    
                    if (stones.length >= 5) {
                        stones.forEach(stone => {
                            const cellIndex = stone.row * this.BOARD_SIZE + stone.col;
                            const cell = document.getElementById('board').children[cellIndex];
                            const stoneElement = cell.querySelector('.stone');
                            if (stoneElement) {
                                stoneElement.classList.add('winning-stone');
                            }
                        });
                        return;
                    }
                }
            }

            getValidMoves() {
                const moves = [];
                for (let i = 0; i < this.BOARD_SIZE; i++) {
                    for (let j = 0; j < this.BOARD_SIZE; j++) {
                        if (this.board[i][j] === this.EMPTY) {
                            moves.push({ row: i, col: j });
                        }
                    }
                }
                return moves;
            }

            isBoardFull() {
                return this.getValidMoves().length === 0;
            }

            updateGameStatus(message, className = '') {
                const statusElement = document.getElementById('game-status');
                statusElement.textContent = message;
                statusElement.className = `game-status ${className}`;
            }

            updateCurrentPlayer() {
                const playerText = this.currentPlayer === this.BLACK ? '黑棋 (玩家)' : '白棋 (AI)';
                document.getElementById('current-player').textContent = playerText;
            }

            setDifficulty(difficulty) {
                this.difficulty = difficulty;
            }

            newGame() {
                this.initBoard();
                this.createBoardUI();
                this.updateCurrentPlayer();
            }
        }

        // 初始化游戏
        let game = new GomokuAI();

        // 游戏控制函数
        function newGame() {
            game.newGame();
        }

        // 难度选择
        document.getElementById('difficulty').addEventListener('change', function() {
            game.setDifficulty(this.value);
        });

        // 键盘快捷键
        document.addEventListener('keydown', function(e) {
            if (e.key === 'n' || e.key === 'N') {
                newGame();
            }
        });

        console.log('🎮 五子棋AI游戏已加载完成!');
        console.log('⌨️  按 N 键开始新游戏');
    </script>
</body>
</html>

这个五子棋AI的核心算法实现基于以下几个关键技术:

  1. Minimax算法 + Alpha-Beta剪枝

  minimax(depth, alpha, beta, isMaximizing) {
      if (depth === 0) {
          return this.evaluateBoard(); // 递归终止条件
      }

      if (isMaximizing) { // AI回合,寻找最大值
          let maxScore = -Infinity;
          for (const move of moves) {
              this.board[move.row][move.col] = this.WHITE;
              const score = this.minimax(depth - 1, alpha, beta, false);
              this.board[move.row][move.col] = this.EMPTY;

              maxScore = Math.max(score, maxScore);
              alpha = Math.max(alpha, score);
              if (beta <= alpha) break; // Alpha-Beta剪枝
          }
          return maxScore;
      }
  }

  工作原理:
  - 递归搜索未来N步的所有可能走法
  - AI层寻找对自己最有利的走法(maximizing)
  - 玩家层假设玩家会选择对AI最不利的走法(minimizing)
  - Alpha-Beta剪枝大幅减少搜索空间

  2. 棋局评估算法

  evaluatePlayer(player) {
      let score = 0;
      const directions = [[0, 1], [1, 0], [1, 1], [1, -1]]; // 四个方向

      for (let i = 0; i < this.BOARD_SIZE; i++) {
          for (let j = 0; j < this.BOARD_SIZE; j++) {
              if (this.board[i][j] === player) {
                  for (const [dx, dy] of directions) {
                      score += this.evaluateLine(i, j, dx, dy, player);
                  }
              }
          }
      }
      return score;
  }

  评分体系:
  SCORES = {
      FIVE: 100000,        // 五连:必胜
      FOUR: 10000,         // 活四:下一步必胜
      BLOCKED_FOUR: 1000,  // 冲四:一端被堵
      THREE: 1000,         // 活三:可形成活四
      BLOCKED_THREE: 100,  // 眠三:一端被堵
      TWO: 100,            // 活二
      BLOCKED_TWO: 10,     // 眠二
      ONE: 10              // 单子
  }

  3. 智能搜索优化

  候选位置筛选:
  getCandidateMoves() {
      // 只搜索已有棋子周围2格内的位置
      for (let i = 0; i < this.BOARD_SIZE; i++) {
          for (let j = 0; j < this.BOARD_SIZE; j++) {
              if (this.board[i][j] !== this.EMPTY) {
                  for (let di = -2; di <= 2; di++) {
                      for (let dj = -2; dj <= 2; dj++) {
                          // 收集有效候选位置
                      }
                  }
              }
          }
      }
  }

  关键位置优先检查:
  findCriticalMove() {
      // 1. 检查AI能否立即获胜
      // 2. 检查是否需要阻止玩家获胜
      // 3. 优先处理威胁和机会
  }

  4. 难度分级系统

  const difficultySettings = {
      easy: { depth: 2, randomness: 0.3 },    // 搜索2步,30%随机性
      medium: { depth: 3, randomness: 0.1 },  // 搜索3步,10%随机性
      hard: { depth: 4, randomness: 0.05 }    // 搜索4步,5%随机性
  };

  难度实现:
  - 搜索深度:越深AI越聪明,但计算时间指数增长
  - 随机性:偶尔选择次优解,避免AI过于完美
  - 计算限制:限制候选位置数量控制响应时间

  5. 核心算法流程

  玩家下棋 → 检查获胜 → AI开始思考
      ↓
  找关键位置(必胜/必防) → 找到则直接下棋
      ↓
  启动Minimax搜索 → 评估所有候选位置
      ↓
  选择最高分位置 → 应用难度调整 → AI下棋

  6. 性能优化技巧

  - 位置剪枝:只考虑棋子周围的有效位置
  - 深度限制:根据难度控制搜索深度
  - Alpha-Beta剪枝:跳过无意义的搜索分支
  - 评估缓存:避免重复计算相同棋局
  - 异步处理:UI不阻塞,增加思考延迟提升体验感

  这个AI实现了从基础博弈论到实际应用的完整流程,能够在合理时间内做出智能决策。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐