当前位置:   article > 正文

【Unity】五子棋AI博弈算法实讲

五子棋ai

大家好!五子棋是我们在学习生活中比较常见的棋类游戏之一。两人对弈,一黑一白,下在棋盘的交叉点上,横竖斜形成五子连珠即可获胜。也许有的同学编程的入门之作就是做一款五子棋,我刚好在项目开发过程中,有涉及到相关玩法的开发。今天我就来做一个系统的、分层次性的概述,跟大家探讨一下五子棋中AI博弈算法

一、五子棋玩法。

五子棋玩法的实现没什么难度,无非就是一个用一个状态机控制战斗逻辑,然后就是简单的交互和显示逻辑。值得探讨的部分,还是AI博弈的部分。想要实现一个(简单/一般/困难/地域)不同难度的AI,作为我们本篇主要讨论的重点。

二、AI博弈算法。

  1. 最简单的随机策略。即每次随机选择一个合法的位置进行下子。此算法更多是用在一方超时,触发的算法。这种算法不太聪明,下子毫无章法,玩家与之对弈,也毫无竞技性和体验性可言。
  2. 基础算法。积分算法比较简单,只考虑当前步的情况,只取当前步的最优位置
    这里给一下算法思路。下子时,不仅需要考虑连珠情况,也要考虑对手棋子的影响,我们分别称之为积极因素和消极因素。
  • 分别设定一个积极因素评估数组score1 = { 7, 35, 800, 15000, 800000 },一个消极因素评估数组score2 = { 7, 15, 400, 2500, 100000 }。(为啥是这些数值,我未做深入探究,有同学感兴趣可做进一步研究)
  • 定义一个方法count_point,用于计算棋盘中各个点的评估分数。从棋盘的初始点方向,依次计算每个点位的积极因素和消极因素。然后筛选出空白可下的点位,进行一个分数排序。然后在分数最高的队列里,随机一个点位,即为最佳位置。

如果想要调整不同水平的难度,可以考虑从前n个空白点位中随机一个。比如可以在分数排序之后,从前1/4个数中随机一个。这样AI的博弈水平就会降到一定程度。只是一个思路,大家也可以有不同的做法。

  1. 高阶算法。α-β剪枝算法是一种经典的博弈树搜索算法,它可以在搜索树中找到最优的下一步棋,并且具有很高的效率和搜索深度。它可以考虑接下来n步之后的棋面情形,来找出当下最优的下一步棋,比上面的积分算法更加的准确。)这里给出一个简单的α-β剪枝算法实例。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class RandomAITest : MonoBehaviour
{
    private const int WINNING_SCORE = 1000000;
    private const int THREE_IN_A_ROW = 100;
    private const int TWO_IN_A_ROW = 10;
    private const int ONE_IN_A_ROW = 1;

    // 获取当前玩家的颜色
    int GetCurrentPlayerColor(int[,] board)
    {
        int count = 0;
        for (int i = 0; i < board.GetLength(0); i++)
        {
            for (int j = 0; j < board.GetLength(1); j++)
            {
                if (board[i, j] != 0)
                {
                    count++;
                }
            }
        }
        return count % 2 == 0 ? 1 : 2;
    }

    // 使用α-β剪枝算法进行决策
    Tuple<int, int> AlphaBetaSearch(int[,] board, int depth)
    {
        Tuple<int, int> bestMove = new Tuple<int, int>(-1, -1);
        int alpha = int.MinValue;
        int beta = int.MaxValue;
        int playerColor = GetCurrentPlayerColor(board);
        Tuple<int, int>[,] actions = GenerateActions(board);
        foreach (Tuple<int, int> action in Shuffle(actions))
        {
            int[,] newBoard = UpdateBoard(board, action.Item1, action.Item2, playerColor);
            int score = MinValue(newBoard, alpha, beta, depth - 1, playerColor);
            if (score > alpha)
            {
                alpha = score;
                bestMove = action;
            }
        }
        return bestMove;
    }

    bool IsGameOver(int[,] board)
    {
        //TODO:判断游戏是否结束
        return false;
    }

    // MaxValue函数
    int MaxValue(int[,] board, int alpha, int beta, int depth, int playerColor)
    {
        if (depth == 0 || IsGameOver(board))
        {
            return Evaluate(board, playerColor);
        }
        int v = int.MinValue;
        Tuple<int, int>[,] actions = GenerateActions(board);
        foreach (Tuple<int, int> action in Shuffle(actions))
        {
            int[,] newBoard = UpdateBoard(board, action.Item1, action.Item2, playerColor);
            int score = MinValue(newBoard, alpha, beta, depth - 1, playerColor);
            if (score > v)
            {
                v = score;
            }
            if (v >= beta)
            {
                return v;
            }
            alpha = Math.Max(alpha, v);
        }
        return v;
    }

    // MinValue函数
    int MinValue(int[,] board, int alpha, int beta, int depth, int playerColor)
    {
        if (depth == 0 || IsGameOver(board))
        {
            return Evaluate(board, playerColor);
        }
        int v = int.MaxValue;
        Tuple<int, int>[,] actions = GenerateActions(board);
        foreach (Tuple<int, int> action in Shuffle(actions))
        {
            int[,] newBoard = UpdateBoard(board, action.Item1, action.Item2, 3 - playerColor);
            int score = MaxValue(newBoard, alpha, beta, depth - 1, playerColor);
            if (score < v)
            {
                v = score;
            }
            if (v <= alpha)
            {
                return v;
            }
            beta = Math.Min(beta, v);
        }
        return v;
    }

    // 评估函数
    int Evaluate(int[,] board, int player)
    {
        int score = 0;

        // Check horizontal
        score += EvaluateDirection(board, player, 0, 1); // right
        score += EvaluateDirection(board, player, 0, -1); // left

        // Check vertical
        score += EvaluateDirection(board, player, 1, 0); // down

        // Check diagonal
        score += EvaluateDirection(board, player, 1, 1); // diagonal down-right
        score += EvaluateDirection(board, player, 1, -1); // diagonal down-left

        return score;
    }

    private int EvaluateDirection(int[,] board, int player, int deltaRow, int deltaCol)
    {
        int score = 0;

        for (int row = 0; row < board.Rows; row++)
        {
            for (int col = 0; col < board.Columns; col++)
            {
                int consecutiveCount = 0;
                int emptyCount = 0;
                bool blocked = false;

                for (int i = 0; i < 4; i++)
                {
                    int currentRow = row + i * deltaRow;
                    int currentCol = col + i * deltaCol;

                    if (!board.IsValidPosition(currentRow, currentCol))
                    {
                        blocked = true;
                        break;
                    }

                    if (board[currentRow, currentCol] == player)
                    {
                        consecutiveCount++;
                    }
                    else if (board[currentRow, currentCol] == Player.None)
                    {
                        emptyCount++;
                    }
                    else
                    {
                        blocked = true;
                        break;
                    }
                }

                if (!blocked)
                {
                    if (consecutiveCount == 4)
                    {
                        return WINNING_SCORE;
                    }
                    else if (consecutiveCount == 3 && emptyCount == 1)
                    {
                        score += THREE_IN_A_ROW;
                    }
                    else if (consecutiveCount == 2 && emptyCount == 2)
                    {
                        score += TWO_IN_A_ROW;
                    }
                    else if (consecutiveCount == 1 && emptyCount == 3)
                    {
                        score += ONE_IN_A_ROW;
                    }
                }
            }
        }

        return score;
    }

    // 生成所有可行的下子位置
    Tuple<int, int>[,] GenerateActions(int[,] board)
    {
        int size = board.GetLength(0);
        Tuple<int, int>[,] actions = new Tuple<int, int>[size, size];
        int count = 0;
        for (int i = 0; i < size; i++)
        {
            for (int j = 0; j < size; j++)
            {
                if (IsValidMove(board, i, j))
                {
                    actions[i, j] = new Tuple<int, int>(i, j);
                    count++;
                }
            }
        }
        Tuple<int, int>[,] result = new Tuple<int, int>[count, 1];
        int index = 0;
        for (int i = 0; i < size; i++)
        {
            for (int j = 0; j < size; j++)
            {
                if (actions[i, j] != null)
                {
                    result[index, 0] = actions[i, j];
                    index++;
                }
            }
        }
        return result;
    }

    // 更新棋盘
    int[,] UpdateBoard(int[,] board, int x, int y, int color)
    {
        int size = board.GetLength(0);
        int[,] newBoard = new int[size, size];
        for (int i = 0; i < size; i++)
        {
            for (int j = 0; j < size; j++)
            {
                newBoard[i, j] = board[i, j];
            }
        }
        newBoard[x, y] = color;
        return newBoard;
    }

    // 洗牌算法
    Tuple<int, int>[,] Shuffle(Tuple<int, int>[,] array)
    {
        System.Random rng = new System.Random();
        int n = array.GetLength(0);
        while (n > 1)
        {
            n--;
            int k = rng.Next(n + 1);
            Tuple<int, int>[,] value = array[k, 0];
             array[k, 0] = array[n, 0];
            array[n, 0] = value;
        }
        return array;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255

它可以根据你调用AlphaBetaSearch方法时,传入depth深度的值,用于找出对于接下来depth步之内最有利的下一步位置。这种博弈算法在对AI水平要求比较高的情况下才使用,AI难度已经非常的高,且对性能要求也比较高。如果不是专门研究五子棋棋法的,不必使用这么高端的算法。用上面第二点的积分算法即可。

三、总结。
以上即时,从不同层次结构对五子棋AI算法的探讨和实现。希望给大家带来一定系统性的认识。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/353719
推荐阅读
相关标签
  

闽ICP备14008679号