• 五子棋简单AI算法(C#版)


    前言:

    本文只提供AI算法,不提供棋盘,因为棋盘不是我写的,不好拿出来分享,望谅解。

    AI水平说明:

    当前AI只能计算当前局面下最优的一步,没有深度,水平一般的普通人很轻易就会被其击败,但是有很大的升级空间,可以以此为基础再行添加算法添加深度,以及剪枝等算法。

    程序运行图片:黑方为AI,我是白色(我是不是太菜了。。)

    AI得分说明:

    当前AI为黑色时水平较高,为白色时需要修改得分表,得分表会影响AI的决策,得分可以自行修改,此得分表并非最佳得分表,但是经过我的测试貌似还可以。ps:这是AI的核心非常重要

    得分表如下:

    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Text;
    6. using System.Threading.Tasks;
    7. namespace 五子棋
    8. {
    9. public static class ChessValueUtil
    10. {
    11. public static Hashtable blackTable = new Hashtable(); //创建一个HashTable实例
    12. public static Hashtable whiteTable = new Hashtable();
    13. //五子棋棋形,最长为7,最短为4(有些棋形必须将空位包含进去)
    14. public static Hashtable getBlackTable()
    15. {
    16. return blackTable;
    17. }
    18. public static Hashtable getWhiteTable()
    19. {
    20. return whiteTable;
    21. }
    22. /**
    23. * *:表示黑子
    24. * -:表示空位
    25. * o:表示白子
    26. */
    27. static ChessValueUtil(){
    28. //还要设置防守方的数值,防止被gank掉
    29. //右边Add的whiteTable值,是防守分数,这样Ai就不会一味的猛冲
    30. //左边:黑方Ai的棋子判断 右边:Ai结束后,玩家可能的棋子判断
    31. blackTable.Add("*****", 100000);//连五
    32. /* */whiteTable.Add("ooooo", 90000);
    33. blackTable.Add("-****-", 5000);//活四
    34. /* */whiteTable.Add("-oooo-", 4500);
    35. blackTable.Add("*-***", 700);//冲四 1
    36. /* */whiteTable.Add("o-ooo", 200);
    37. blackTable.Add("***-*", 700);//冲四 1 反向
    38. /* */whiteTable.Add("ooo-o", 200);
    39. blackTable.Add("-****o", 900);//冲四 2
    40. /* */whiteTable.Add("-oooo*", 250);
    41. blackTable.Add("o****-", 900);//冲四 2 反向
    42. /* */whiteTable.Add("*oooo-", 250);
    43. blackTable.Add("**-**", 700);//冲四 3
    44. /* */whiteTable.Add("oo-oo", 200);
    45. blackTable.Add("-***-", 600);//活三 1
    46. /* */whiteTable.Add("-ooo-", 150);
    47. blackTable.Add("-*-**-", 150);//活三 2
    48. /* */whiteTable.Add("-o-oo-", 50);
    49. blackTable.Add("-**-*-", 150);//活三 2 反向
    50. /* */whiteTable.Add("-oo-o-", 50);
    51. blackTable.Add("--***o", 120);//眠三 1
    52. /* */whiteTable.Add("--ooo*", 30);
    53. blackTable.Add("o***--", 120);//眠三 1 反向
    54. /* */whiteTable.Add("*ooo--", 30);
    55. blackTable.Add("-*-**o", 80);//眠三 2
    56. /* */whiteTable.Add("-o-oo*", 15);
    57. blackTable.Add("o**-*-", 80);//眠三 2 反向
    58. /* */whiteTable.Add("*oo-o-", 15);
    59. blackTable.Add("-**-*o", 60);//眠三 3
    60. /* */whiteTable.Add("-oo-o*", 10);
    61. blackTable.Add("o*-**-", 60);//眠三 3 反向
    62. /* */whiteTable.Add("*o-oo-", 10);
    63. blackTable.Add("*--**", 60);//眠三 4
    64. /* */whiteTable.Add("o--oo", 10);
    65. blackTable.Add("**--*", 60);//眠三 4 反向
    66. /* */whiteTable.Add("oo--o", 10);
    67. blackTable.Add("*-*-*", 60);//眠三 5
    68. /* */whiteTable.Add("o-o-o", 10);
    69. blackTable.Add("o-***-o", 60);//眠三 6
    70. /* */whiteTable.Add("*-ooo-*", 2);
    71. blackTable.Add("--**--", 50);//活二 1
    72. /* */whiteTable.Add("--oo--", 2);
    73. blackTable.Add("-*-*-", 20);//活二 2
    74. /* */whiteTable.Add("-o-o-", 2);
    75. blackTable.Add("*--*", 20);//活二 3
    76. /* */whiteTable.Add("o--o", 2);
    77. blackTable.Add("---**o", 10);//眠二 1
    78. /* */whiteTable.Add("---oo*", 1);
    79. blackTable.Add("o**---", 10);//眠二 1 反向
    80. /* */whiteTable.Add("*oo---", 1);
    81. blackTable.Add("--*-*o", 10);//眠二 2
    82. /* */whiteTable.Add("--o-o*", 1 );
    83. blackTable.Add("o*-*--", 10);//眠二 2 反向
    84. /* */whiteTable.Add("*o-o--", 1);
    85. blackTable.Add("-*--*o", 10);//眠二 3
    86. /* */whiteTable.Add("-o--o*", 1);
    87. blackTable.Add("o*--*-", 10);//眠二 3 反向
    88. /* */whiteTable.Add("*o--o-", 1);
    89. blackTable.Add("*---*", 10);//眠二 4
    90. /* */whiteTable.Add("o---o", 1);
    91. }
    92. }
    93. }

     AI算法思路讲解:

    首先我们需要知道输入和输出,给输入当前棋盘的二维数组,从而让他返回落点坐标。落点坐标有x,y两个值,我们可以先创建一个点的结构体,包括点信息,和当前点的分值:

    public struct Point
            {
                public int value;
                public int x;
                public int y;

            }

     二维数组中存储的是数字,而我们得分表中用的字符串,所以我们需要一个转换的方法,传入棋盘中某一条链,返回该链字符串形式:

    public static string ChessChainTostring(int[] CheckChain)
            {
                string pool = "";
                foreach (int chessColor in CheckChain)
                {
                    if (chessColor == 0)
                    {
                        pool += "-";
                    }
                    else if (chessColor == 1)
                    {
                        pool += "*";
                    }
                    else if (chessColor == 2)
                    {
                        pool += "o";
                    }
                    else
                    {
                        //放入一个hash表中不存在的字符
                        //这样后面算权值的时候就不会把它加进去了
                        pool += "#";
                    }
                }
                return pool;
            }

     核心思想:

    此处为我自己的思考方式,因为我实在是搞懂别人是怎么算的,思路很可能不一样,但是我的思路很简单,代码也少,但是运算量很大,如果你很有兴趣可以自己试着优化。

    最后一步肯定是依次计算棋盘中每一个点的分值,并从中选取得分最高的点返回(如果得分相同按理来说应该写个方法随机返回,我这里没写,默认返回第一个)

    int[,] map = new int[len, len];
                for (int i = 0; i < len; i++)
                {
                    for (int j = 0; j < len; j++)
                    {
                        if (ChessBoard[i, j] == 0)
                        {
                            map[i,j]=getScore(ChessBoard, i, j);
                            if (map[i, j] > max)
                            {
                                max = map[i, j];
                                point.value = max;
                                point.x = i;
                                point.y = j;
                            }
                        }
                    }
                }

    计算分值思路:

    假设在当前点下棋,拿到当前点位置横竖撇奈四条线上所有链条,转化为字符串并与得分表中的字符串进行比对,如果包含得分表中字符串就加上相应的分数,四条链每一条都要进行判断,最后得到在当前点落点的分值。

    假设当前点不下棋,通过同样方式计算得到原本该点四条链上的分值。

    落点的分值-不落点的分值=当前点的得分

    当前点得分有两种,因为我们有两个得分表(一黑一白),一种是进攻分,一种是防守分,都要进行计算,算出当前点的进攻分和防守分的总和,在减去两种原始得分,才能得出走该点真实分值。

    算完后不要忘记将棋盘还原。

    public static int getScore(int[,] ChessBoard, int x, int y)
            {
                int oldBlackValue=getValue(ChessBoard, x, y,1);
                int oldWhiteValue = getValue(ChessBoard, x, y, 2);
                ChessBoard[x, y] = 1;
                int atkValue=getValue(ChessBoard, x, y,1);
                ChessBoard[x, y] = 2;
                int defValue= getValue(ChessBoard, x, y,2);
                int score = atkValue + defValue - oldBlackValue- oldWhiteValue;

                //复原棋盘
                ChessBoard[x, y] = 0;
                return score;
            }

    public static int getValue(int[,] ChessBoard,int x,int y,int color)
            {
                int value = 0;
                ArrayList Chains=getChains(ChessBoard, x, y);
                foreach (int[] chain in Chains)
                {
                    string str=ChessChainTostring(chain);
                    Hashtable table;
                    if (color == 1)
                    {
                        table = ChessValueUtil.getBlackTable();
                    }
                    else
                    {
                        table = ChessValueUtil.getWhiteTable();
                    }
                    ICollection keys = table.Keys;
                    
                    foreach (string key in keys)
                    {
                        for (int i = 0; i < str.Length - key.Length+1; i++)
                        {
                            if (str.Substring(i, key.Length) == key)
                            {
                                value += (int)table[key];
                            }
                        }
                    }
                }
                return value;
            }

     public static ArrayList getChains(int[,] ChessBoard,int x,int y)
            {
                
                int len = 15;
                
                ArrayList Chains = new ArrayList();
                //横
                int[] heng = new int[len];
                for (int i = 0; i < len; i++)
                {
                    heng[i]=ChessBoard[i, y];
                }
                Chains.Add(heng);
                //竖
                int[] shu = new int[len];
                for (int i = 0; i < len; i++)
                {
                    shu[i] = ChessBoard[x, i];
                }
                Chains.Add(shu);
                //撇,x+,y-
                int[] pie = new int[len];
                int tmpX = x;
                int tmpY = y;
                while (tmpX > 0 && tmpY < len - 1)
                {
                    tmpX--;
                    tmpY++;
                }
                int k = 0;
                while (tmpX <= len - 1 && tmpY >= 0)
                {
                    pie[k++] = ChessBoard[tmpX, tmpY];
                    tmpX++;
                    tmpY--;
                }
                if (k < len)
                {
                    pie[k] = -1;
                }
                
                Chains.Add(pie);
                //捺
                int[] nai= new int[len];
                tmpX = x;
                tmpY = y;
                while (tmpX > 0 && tmpY>0)
                {
                    tmpX--;
                    tmpY--;
                }
                k = 0;
                while (tmpX <= len - 1 && tmpY <= len - 1)
                {
                    nai[k++] = ChessBoard[tmpX, tmpY];
                    tmpX++;
                    tmpY++;
                }
                if (k < len)
                {
                    nai[k] = -1;
                }
                Chains.Add(nai);
                return Chains;
            }

     这段代码判断当前棋盘为空时直接返回中间点坐标,其实最好还是不要写在AI里,因为每次都要循环一遍棋盘,最好是写在外面:

    int flag = 0;
                for (int i = 0; i < len; i++)
                {
                    for (int j = 0; j < len; j++)
                    {
                        
                        if (ChessBoard[i, j] != 0)
                        {
                            flag = 1;
                            break;

                        }
                        if (flag == 1)
                        {
                            break;
                        }
                    }
                }
                if (flag == 0)
                {
                    point.value = 0;
                    point.x = 7;
                    point.y = 7;
                    return point;
                }

    完整代码(代码写的很垃圾,见谅):

    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Text;
    6. using System.Threading.Tasks;
    7. using System.Windows.Forms;
    8. namespace 五子棋
    9. {
    10. static class ChessUtil
    11. {
    12. public struct Point
    13. {
    14. public int value;
    15. public int x;
    16. public int y;
    17. }
    18. /**
    19. * 将棋子的颜色序列转换为字符序列
    20. * 这样才能放入hashtable中进行比较
    21. */
    22. /**
    23. * *:表示黑子
    24. * -:表示空位
    25. * o:表示白子
    26. */
    27. public static string ChessChainTostring(int[] CheckChain)
    28. {
    29. string pool = "";
    30. foreach (int chessColor in CheckChain)
    31. {
    32. if (chessColor == 0)
    33. {
    34. pool += "-";
    35. }
    36. else if (chessColor == 1)
    37. {
    38. pool += "*";
    39. }
    40. else if (chessColor == 2)
    41. {
    42. pool += "o";
    43. }
    44. else
    45. {
    46. //放入一个hash表中不存在的字符
    47. //这样后面算权值的时候就不会把它加进去了
    48. pool += "#";
    49. }
    50. }
    51. return pool;
    52. }
    53. public static ArrayList getChains(int[,] ChessBoard,int x,int y)
    54. {
    55. int len = 15;
    56. ArrayList Chains = new ArrayList();
    57. //横
    58. int[] heng = new int[len];
    59. for (int i = 0; i < len; i++)
    60. {
    61. heng[i]=ChessBoard[i, y];
    62. }
    63. Chains.Add(heng);
    64. //竖
    65. int[] shu = new int[len];
    66. for (int i = 0; i < len; i++)
    67. {
    68. shu[i] = ChessBoard[x, i];
    69. }
    70. Chains.Add(shu);
    71. //撇,x+,y-
    72. int[] pie = new int[len];
    73. int tmpX = x;
    74. int tmpY = y;
    75. while (tmpX > 0 && tmpY < len - 1)
    76. {
    77. tmpX--;
    78. tmpY++;
    79. }
    80. int k = 0;
    81. while (tmpX <= len - 1 && tmpY >= 0)
    82. {
    83. pie[k++] = ChessBoard[tmpX, tmpY];
    84. tmpX++;
    85. tmpY--;
    86. }
    87. if (k < len)
    88. {
    89. pie[k] = -1;
    90. }
    91. Chains.Add(pie);
    92. //捺
    93. int[] nai= new int[len];
    94. tmpX = x;
    95. tmpY = y;
    96. while (tmpX > 0 && tmpY>0)
    97. {
    98. tmpX--;
    99. tmpY--;
    100. }
    101. k = 0;
    102. while (tmpX <= len - 1 && tmpY <= len - 1)
    103. {
    104. nai[k++] = ChessBoard[tmpX, tmpY];
    105. tmpX++;
    106. tmpY++;
    107. }
    108. if (k < len)
    109. {
    110. nai[k] = -1;
    111. }
    112. Chains.Add(nai);
    113. return Chains;
    114. }
    115. public static int getValue(int[,] ChessBoard,int x,int y,int color)
    116. {
    117. int value = 0;
    118. ArrayList Chains=getChains(ChessBoard, x, y);
    119. foreach (int[] chain in Chains)
    120. {
    121. string str=ChessChainTostring(chain);
    122. Hashtable table;
    123. if (color == 1)
    124. {
    125. table = ChessValueUtil.getBlackTable();
    126. }
    127. else
    128. {
    129. table = ChessValueUtil.getWhiteTable();
    130. }
    131. ICollection keys = table.Keys;
    132. foreach (string key in keys)
    133. {
    134. for (int i = 0; i < str.Length - key.Length+1; i++)
    135. {
    136. if (str.Substring(i, key.Length) == key)
    137. {
    138. value += (int)table[key];
    139. }
    140. }
    141. }
    142. }
    143. return value;
    144. }
    145. public static int getScore(int[,] ChessBoard, int x, int y)
    146. {
    147. int oldBlackValue=getValue(ChessBoard, x, y,1);
    148. int oldWhiteValue = getValue(ChessBoard, x, y, 2);
    149. ChessBoard[x, y] = 1;
    150. int atkValue=getValue(ChessBoard, x, y,1);
    151. ChessBoard[x, y] = 2;
    152. int defValue= getValue(ChessBoard, x, y,2);
    153. int score = atkValue + defValue - oldBlackValue- oldWhiteValue;
    154. ChessBoard[x, y] = 0;
    155. return score;
    156. }
    157. public static Point getScoreMap(int[,] ChessBoard)
    158. {
    159. Point point;
    160. int max = 0;
    161. point.value = 0;
    162. point.x = 0;
    163. point.y = 0;
    164. int len = 15;
    165. int flag = 0;
    166. for (int i = 0; i < len; i++)
    167. {
    168. for (int j = 0; j < len; j++)
    169. {
    170. if (ChessBoard[i, j] != 0)
    171. {
    172. flag = 1;
    173. break;
    174. }
    175. if (flag == 1)
    176. {
    177. break;
    178. }
    179. }
    180. }
    181. if (flag == 0)
    182. {
    183. point.value = 0;
    184. point.x = 7;
    185. point.y = 7;
    186. return point;
    187. }
    188. int[,] map = new int[len, len];
    189. for (int i = 0; i < len; i++)
    190. {
    191. for (int j = 0; j < len; j++)
    192. {
    193. if (ChessBoard[i, j] == 0)
    194. {
    195. map[i,j]=getScore(ChessBoard, i, j);
    196. if (map[i, j] > max)
    197. {
    198. max = map[i, j];
    199. point.value = max;
    200. point.x = i;
    201. point.y = j;
    202. }
    203. }
    204. }
    205. }
    206. return point;
    207. }
    208. }
    209. }

    最后一个方法本来是计算当所有点坐标的分值图,从而进行二次甚至是三次递归的,但是因为是简单AI,所以算一次就返回吧,其实有些运算都是可以避免的,比如如果某点附近5步之内没有点,其实可以直接跳过的,但是我没进行优化,所以运算量是相当大了,大家自己试着优化吧。

    如果是白棋的话,翻转一下棋盘传进去,就能获得一个偏向进攻的白棋,但是可能会出现某些问题,还有就是传入的棋盘最好是备份的,尽量不要把原本的棋盘传进去,就这样吧。

    如果你找到了更好的计算当前点分数的方法,请务必给我留言,感激不尽!

  • 相关阅读:
    基于FPGA的Hamiton方程--辛几何算法实现(全网唯一)
    QT:画一个简单的时钟(坐标系移动、坐标系旋转、保存坐标系、恢复坐标系)
    Java 面试真题
    Leetcode_48:旋转图像
    NBT:快准全!geNomad——宏病毒组鉴定新工具
    IO流的讲解(3)
    数学小抄:线性回归与协方差
    使用.NET源生成器(SG)实现一个自动注入的生成器
    统计遗传学:第四章,GWAS分析
    微信小程序使用 scss
  • 原文地址:https://blog.csdn.net/Starry_error/article/details/125498493