从零实现Js五子棋游戏:完整教程

从零实现五子棋AI游戏:完整教程

在这篇教程中,我们将一步步实现一个具有AI对手的五子棋游戏。通过这个项目,你将学习到:

  • 如何使用HTML和CSS创建游戏界面
  • 如何实现五子棋的基本规则和胜负判定
  • 如何开发一个简单但有效的AI对手

在这里插入图片描述

1. 游戏界面设计

1.1 HTML结构

我们的游戏界面包含三个主要部分:

  • 标题
  • 棋盘
  • 状态显示和控制按钮
<div class="container">
    <h1>五子棋</h1>
    <div id="board"></div>
    <div class="status" id="status">黑方回合</div>
    <button class="reset-btn" onclick="resetGame()">重新开始</button>
</div>

1.2 CSS样式设计

我们使用CSS Grid来创建棋盘,使用渐变效果让棋子看起来更立体:

#board {
    
    
    display: grid;
    grid-template-columns: repeat(15, 30px);
    grid-template-rows: repeat(15, 30px);
    gap: 0px;
    background-color: #DEB887;
    padding: 10px;
    border: 2px solid #8B4513;
}

.piece {
    
    
    width: 26px;
    height: 26px;
    border-radius: 50%;
    position: absolute;
}

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

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

2. 游戏核心逻辑

2.1 初始化游戏

首先,我们需要创建棋盘并初始化游戏状态:

const BOARD_SIZE = 15;
let currentPlayer = 'black';
let gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(''));
let gameOver = false;

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

2.2 落子判定

在每次落子时,我们需要:

  1. 验证落子位置是否合法
  2. 更新棋盘状态
  3. 检查是否获胜
  4. 切换玩家
function makeMove(row, col) {
    
    
    gameBoard[row][col] = currentPlayer;
    
    const cell = document.querySelector(`[data-row="${
      
      row}"][data-col="${
      
      col}"]`);
    const piece = document.createElement('div');
    piece.className = `piece ${
      
      currentPlayer}`;
    cell.appendChild(piece);
    
    if (checkWin(row, col)) {
    
    
        document.getElementById('status').textContent = 
            `${
      
      currentPlayer === 'black' ? '黑方' : '电脑'}胜利!`;
        gameOver = true;
        return;
    }
    
    currentPlayer = currentPlayer === 'black' ? 'white' : 'black';
    if (!gameOver) {
    
    
        document.getElementById('status').textContent = 
            `${
      
      currentPlayer === 'black' ? '黑方' : '电脑'}回合`;
    }
}

2.3 胜负判定

五子棋的胜负判定需要检查四个方向的连子情况:

  • 水平
  • 垂直
  • 主对角线
  • 副对角线
function checkWin(row, col) {
    
    
    const directions = [
        [[0, 1], [0, -1]], // 水平
        [[1, 0], [-1, 0]], // 垂直
        [[1, 1], [-1, -1]], // 主对角线
        [[1, -1], [-1, 1]] // 副对角线
    ];
    
    return directions.some(dir => {
    
    
        const count = 1 + countDirection(row, col, dir[0]) + countDirection(row, col, dir[1]);
        return count >= 5;
    });
}

function countDirection(row, col, [dx, dy]) {
    
    
    let count = 0;
    let r = row + dx;
    let c = col + dy;
    
    while (
        r >= 0 && r < BOARD_SIZE && 
        c >= 0 && c < BOARD_SIZE && 
        gameBoard[r][c] === currentPlayer
    ) {
    
    
        count++;
        r += dx;
        c += dy;
    }
    
    return count;
}

3. AI实现

3.1 位置评估

AI的核心是评估每个可能的落子位置的价值。我们考虑以下因素:

  • 连子数量(五连、四连、三连等)
  • 位置的中心性
  • 攻防平衡
function evaluatePosition(row, col, player) {
    
    
    if (gameBoard[row][col] !== '') return -1;
    
    let score = 0;
    const directions = [
        [[0, 1], [0, -1]], // 水平
        [[1, 0], [-1, 0]], // 垂直
        [[1, 1], [-1, -1]], // 对角线
        [[1, -1], [-1, 1]] // 反对角线
    ];

    // 临时落子评估
    gameBoard[row][col] = player;
    
    directions.forEach(dir => {
    
    
        const count = 1 + countDirection(row, col, dir[0]) + countDirection(row, col, dir[1]);
        
        // 评分规则
        if (count >= 5) score += 100000;     // 五连(必胜)
        else if (count === 4) score += 10000; // 四连(高威胁)
        else if (count === 3) score += 1000;  // 三连
        else if (count === 2) score += 100;   // 两连
    });
    
    gameBoard[row][col] = '';
    
    // 考虑中心位置价值
    const centerDist = Math.abs(row - 7) + Math.abs(col - 7);
    score += (14 - centerDist) * 10;
    
    return score;
}

3.2 AI决策

AI通过评估所有可能的位置来选择最佳落子点:

function makeAIMove() {
    
    
    let bestScore = -1;
    let bestMove = null;
    
    // 评估所有位置
    for (let i = 0; i < BOARD_SIZE; i++) {
    
    
        for (let j = 0; j < BOARD_SIZE; j++) {
    
    
            if (gameBoard[i][j] !== '') continue;
            
            // 评估进攻和防守
            const aiScore = evaluatePosition(i, j, 'white');
            const playerScore = evaluatePosition(i, j, 'black');
            
            // 选择最高分位置,略微偏重防守
            const score = Math.max(aiScore, playerScore * 1.1);
            
            if (score > bestScore) {
    
    
                bestScore = score;
                bestMove = [i, j];
            }
        }
    }
    
    if (bestMove) {
    
    
        makeMove(bestMove[0], bestMove[1]);
    }
}

4. 完整代码

以下是完整的游戏代码:

<!DOCTYPE html>
<html>
<head>
    <title>五子棋</title>
    <style>
        .container {
      
      
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
            font-family: Arial, sans-serif;
        }
        
        #board {
      
      
            display: grid;
            grid-template-columns: repeat(15, 30px);
            grid-template-rows: repeat(15, 30px);
            gap: 0px;
            background-color: #DEB887;
            padding: 10px;
            border: 2px solid #8B4513;
        }
        
        .cell {
      
      
            width: 30px;
            height: 30px;
            border: 1px solid #000;
            box-sizing: border-box;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            cursor: pointer;
        }
        
        .piece {
      
      
            width: 26px;
            height: 26px;
            border-radius: 50%;
            position: absolute;
        }
        
        .black {
      
      
            background: radial-gradient(circle at 30% 30%, #666, #000);
        }
        
        .white {
      
      
            background: radial-gradient(circle at 30% 30%, #fff, #ccc);
        }
        
        .status {
      
      
            margin-top: 20px;
            font-size: 20px;
            font-weight: bold;
        }
        
        .reset-btn {
      
      
            margin-top: 20px;
            padding: 10px 20px;
            font-size: 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        
        .reset-btn:hover {
      
      
            background-color: #45a049;
        }

        .thinking {
      
      
            color: #666;
            font-style: italic;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>五子棋</h1>
        <div id="board"></div>
        <div class="status" id="status">黑方回合</div>
        <button class="reset-btn" onclick="resetGame()">重新开始</button>
    </div>

    <script>
        const BOARD_SIZE = 15;
        let currentPlayer = 'black';
        let gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(''));
        let gameOver = false;

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

        function handleMove(e) {
      
      
            if (gameOver || currentPlayer !== 'black') return;
            
            const row = parseInt(e.target.dataset.row);
            const col = parseInt(e.target.dataset.col);
            
            if (gameBoard[row][col] !== '') return;
            
            makeMove(row, col);
            
            if (!gameOver) {
      
      
                document.getElementById('status').textContent = '电脑思考中...';
                document.getElementById('status').classList.add('thinking');
                
                setTimeout(() => {
      
      
                    makeAIMove();
                    document.getElementById('status').classList.remove('thinking');
                }, 500);
            }
        }

        function makeMove(row, col) {
      
      
            gameBoard[row][col] = currentPlayer;
            
            const cell = document.querySelector(`[data-row="${ 
        row}"][data-col="${ 
        col}"]`);
            const piece = document.createElement('div');
            piece.className = `piece ${ 
        currentPlayer}`;
            cell.appendChild(piece);
            
            if (checkWin(row, col)) {
      
      
                document.getElementById('status').textContent = 
                    `${ 
        currentPlayer === 'black' ? '黑方' : '电脑'}胜利!`;
                gameOver = true;
                return;
            }
            
            currentPlayer = currentPlayer === 'black' ? 'white' : 'black';
            if (!gameOver) {
      
      
                document.getElementById('status').textContent = 
                    `${ 
        currentPlayer === 'black' ? '黑方' : '电脑'}回合`;
            }
        }

        function checkWin(row, col) {
      
      
            const directions = [
                [[0, 1], [0, -1]], // 水平
                [[1, 0], [-1, 0]], // 垂直
                [[1, 1], [-1, -1]], // 对角线
                [[1, -1], [-1, 1]] // 反对角线
            ];
            
            return directions.some(dir => {
      
      
                const count = 1 + countDirection(row, col, dir[0]) + countDirection(row, col, dir[1]);
                return count >= 5;
            });
        }

        function countDirection(row, col, [dx, dy]) {
      
      
            let count = 0;
            let r = row + dx;
            let c = col + dy;
            
            while (
                r >= 0 && r < BOARD_SIZE && 
                c >= 0 && c < BOARD_SIZE && 
                gameBoard[r][c] === currentPlayer
            ) {
      
      
                count++;
                r += dx;
                c += dy;
            }
            
            return count;
        }

        function evaluatePosition(row, col, player) {
      
      
            if (gameBoard[row][col] !== '') return -1;
            
            let score = 0;
            const directions = [
                [[0, 1], [0, -1]], // 水平
                [[1, 0], [-1, 0]], // 垂直
                [[1, 1], [-1, -1]], // 对角线
                [[1, -1], [-1, 1]] // 反对角线
            ];

            gameBoard[row][col] = player;
            
            directions.forEach(dir => {
      
      
                const count = 1 + countDirection(row, col, dir[0]) + countDirection(row, col, dir[1]);
                
                if (count >= 5) score += 100000;
                else if (count === 4) score += 10000;
                else if (count === 3) score += 1000;
                else if (count === 2) score += 100;
            });
            
            gameBoard[row][col] = '';
            
            const centerDist = Math.abs(row - 7) + Math.abs(col - 7);
            score += (14 - centerDist) * 10;
            
            return score;
        }
 
        function makeAIMove() {
      
      
            let bestScore = -1;
            let bestMove = null;
            
            for (let i = 0; i < BOARD_SIZE; i++) {
      
      
                for (let j = 0; j < BOARD_SIZE; j++) {
      
      
                    if (gameBoard[i][j] !== '') continue;
                    
                    const aiScore = evaluatePosition(i, j, 'white');
                    const playerScore = evaluatePosition(i, j, 'black');
                    
                    const score = Math.max(aiScore, playerScore * 1.1);
                    
                    if (score > bestScore) {
      
      
                        bestScore = score;
                        bestMove = [i, j];
                    }
                }
            }
            
            if (bestMove) {
      
      
                makeMove(bestMove[0], bestMove[1]);
            }
        }

        function resetGame() {
      
      
            gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(''));
            currentPlayer = 'black';
            gameOver = false;
            document.getElementById('status').textContent = '黑方回合';
            document.getElementById('status').classList.remove('thinking');
            createBoard();
        }

        createBoard();
    </script>
</body>
</html>

5. AI策略深入分析

5.1 评分系统设计

AI的评分系统是整个游戏的核心,我们采用了分层的评分策略:

  1. 连子评分

    • 五连:100000分(必胜)
    • 四连:10000分(高威胁)
    • 三连:1000分(中等威胁)
    • 二连:100分(潜在威胁)
  2. 位置评分

    • 基于到棋盘中心的距离
    • 距离每增加1,分数减少10分
    • 最大额外得分:140分(中心点)

5.2 AI决策过程

AI的决策过程可以分为以下步骤:

  1. 遍历所有空位

    for (let i = 0; i < BOARD_SIZE; i++) {
          
          
        for (let j = 0; j < BOARD_SIZE; j++) {
          
          
            if (gameBoard[i][j] !== '') continue;
            // 评估该位置
        }
    }
    
  2. 双重评估

    • 评估AI落子后的得分
    • 评估防守玩家可能落子的得分
    const aiScore = evaluatePosition(i, j, 'white');
    const playerScore = evaluatePosition(i, j, 'black');
    
  3. 权衡攻防

    • 取攻防得分的最大值
    • 给防守分数略微加权(1.1倍)
    const score = Math.max(aiScore, playerScore * 1.1);
    

5.3 AI优化方向

当前AI还可以在以下方面进行优化:

  1. 深度搜索

    • 实现极小极大算法
    • 添加Alpha-Beta剪枝
    • 考虑更多步数的局面
  2. 模式识别

    • 识别特定的棋型(活三、冲四等)
    • 为不同棋型分配更精确的权重
  3. 开局库

    • 添加常用开局
    • 在开局阶段使用预设策略
  4. 性能优化

    • 实现启发式搜索
    • 优化评估函数的计算效率
    • 实现缓存机制

6. 用户交互优化

6.1 当前实现的交互特性

  1. 状态反馈

    • 显示当前回合信息
    • 显示游戏结果
    • 显示AI思考状态
  2. 操作控制

    • 鼠标悬停效果
    • 非法落子预防
    • 重新开始功能

6.2 可能的改进方向

  1. 游戏设置

    • 难度选择
    • 先手选择
    • 棋盘大小调整
  2. 游戏功能

    • 悔棋功能
    • 保存/加载游戏
    • 复盘功能
  3. 界面优化

    • 音效反馈
    • 落子动画
    • 最后落子标记
    • 胜利线标记

7. 总结

这个五子棋AI实现虽然简单,但包含了游戏开发的几个重要方面:

  1. 界面设计

    • 清晰的视觉层次
    • 响应式的用户交互
    • 及时的状态反馈
  2. 游戏逻辑

    • 状态管理
    • 规则判定
    • 胜负判定
  3. AI实现

    • 局面评估
    • 决策算法
    • 攻防平衡

这个实现为进一步优化提供了良好的基础。通过添加更复杂的算法和功能,可以将其发展成一个更强大的五子棋游戏。

猜你喜欢

转载自blog.csdn.net/exlink2012/article/details/143206367