当前位置:   article > 正文

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

五子棋ai

前言:

本文只提供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步之内没有点,其实可以直接跳过的,但是我没进行优化,所以运算量是相当大了,大家自己试着优化吧。

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

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

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号