Ai.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. //下棋业务核心类,与界面棋盘对应,业务放在这里,可以和界面代码分离
  2. import 'dart:core';
  3. import 'dart:core';
  4. import 'package:gobang/bridge/ChessShape.dart';
  5. import 'package:gobang/flyweight/ChessFlyweightFactory.dart';
  6. import 'package:gobang/flyweight/Position.dart';
  7. class Ai{
  8. Ai._();
  9. static Ai? _ai;
  10. static Ai getInstance(){
  11. if(_ai==null){
  12. _ai = Ai._();
  13. }
  14. return _ai!;
  15. }
  16. static var CHESSBOARD_SIZE = 15;
  17. static var FIRST = 1;//先手,-1表示机器,1表示人类,与Position类中的对应
  18. var chessboard = List.generate(CHESSBOARD_SIZE, (i) => List.filled(CHESSBOARD_SIZE, 0, growable: false), growable: false);//与界面棋盘对应,0代表空,-1代表机器,1代表人类
  19. var score = List.generate(CHESSBOARD_SIZE, (i) => List.filled(CHESSBOARD_SIZE, 0, growable: false), growable: false);//每个位置得分
  20. void init(){
  21. FIRST = 1;//默认人类先手
  22. for(int i = 0; i < CHESSBOARD_SIZE; i++){
  23. for(int j = 0; j < CHESSBOARD_SIZE; j++){
  24. chessboard[i][j] = 0;
  25. score[i][j] = 0;
  26. }
  27. }
  28. }
  29. //落子
  30. void addChessman(int x, int y, int owner){
  31. chessboard[x][y] = owner;
  32. print("$x,$y,$owner");
  33. }
  34. //判断落子位置是否合法
  35. bool isLegal(int x, int y){
  36. if(x >=0 && x < CHESSBOARD_SIZE && y >= 0 && y < CHESSBOARD_SIZE && chessboard[x][y] == 0){
  37. return true;
  38. }
  39. return false;
  40. }
  41. //判断哪方赢了(必定有刚落的子引发,因此只需判断刚落子的周围),owner为-1代表机器,owner为1代表人类
  42. bool isWin(int x, int y, int owner){
  43. int sum = 0;
  44. //判断横向左边
  45. for(int i = x - 1; i >= 0; i--){
  46. if(chessboard[i][y] == owner){sum++;}
  47. else{break;}
  48. }
  49. //判断横向右边
  50. for(int i = x + 1; i < CHESSBOARD_SIZE; i++){
  51. if(chessboard[i][y] == owner){sum++;}
  52. else{break;}
  53. }
  54. if(sum >= 4) {return true;}
  55. sum = 0;
  56. //判断纵向上边
  57. for(int i = y - 1; i >= 0; i--){
  58. if(chessboard[x][i] == owner){sum++;}
  59. else{break;}
  60. }
  61. //判断纵向下边
  62. for(int i = y + 1; i < CHESSBOARD_SIZE; i++){
  63. if(chessboard[x][i] == owner){sum++;}
  64. else{break;}
  65. }
  66. if(sum >= 4) {return true;}
  67. sum = 0;
  68. //判断左上角到右下角方向上侧
  69. for(int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j-- ){
  70. if(chessboard[i][j] == owner){sum++;}
  71. else{break;}
  72. }
  73. //判断左上角到右下角方向下侧
  74. for(int i = x + 1, j = y + 1; i < CHESSBOARD_SIZE && j < CHESSBOARD_SIZE; i++, j++ ){
  75. if(chessboard[i][j] == owner){sum++;}
  76. else{break;}
  77. }
  78. if(sum >= 4) {return true;}
  79. sum = 0;
  80. //判断右上角到左下角方向上侧
  81. for(int i = x + 1, j = y - 1; i < CHESSBOARD_SIZE && j >= 0; i++, j-- ){
  82. if(chessboard[i][j] == owner){sum++;}
  83. else{break;}
  84. }
  85. //判断右上角到左下角方向下侧
  86. for(int i = x - 1, j = y + 1; i >= 0 && j < CHESSBOARD_SIZE; i--, j++ ){
  87. if(chessboard[i][j] == owner){sum++;}
  88. else{break;}
  89. }
  90. if(sum >= 4) {return true;}
  91. return false;
  92. }
  93. //【【【【【*******整个游戏的核心*******】】】】】______确定机器落子位置
  94. //使用五元组评分算法,该算法参考博客地址:https://blog.csdn.net/u011587401/article/details/50877828
  95. //算法思路:对15X15的572个五元组分别评分,一个五元组的得分就是该五元组为其中每个位置贡献的分数,
  96. // 一个位置的分数就是其所在所有五元组分数之和。所有空位置中分数最高的那个位置就是落子位置。
  97. Position searchPosition(){
  98. //每次都初始化下score评分数组
  99. for(int i = 0; i < CHESSBOARD_SIZE; i++){
  100. for(int j = 0; j < CHESSBOARD_SIZE; j++){
  101. score[i][j] = 0;
  102. }
  103. }
  104. //每次机器找寻落子位置,评分都重新算一遍(虽然算了很多多余的,因为上次落子时候算的大多都没变)
  105. //先定义一些变量
  106. int humanChessmanNum = 0;//五元组中的黑棋数量
  107. int machineChessmanNum = 0;//五元组中的白棋数量
  108. int tupleScoreTmp = 0;//五元组得分临时变量
  109. int goalX = -1;//目标位置x坐标
  110. int goalY = -1;//目标位置y坐标
  111. int maxScore = -1;//最大分数
  112. //1.扫描横向的15个行
  113. for(int i = 0; i < 15; i++){
  114. for(int j = 0; j < 11; j++){
  115. int k = j;
  116. while(k < j + 5){
  117. if(chessboard[i][k] == -1) machineChessmanNum++;
  118. else if(chessboard[i][k] == 1)humanChessmanNum++;
  119. k++;
  120. }
  121. tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
  122. //为该五元组的每个位置添加分数
  123. for(k = j; k < j + 5; k++){
  124. score[i][k] += tupleScoreTmp;
  125. }
  126. //置零
  127. humanChessmanNum = 0;//五元组中的黑棋数量
  128. machineChessmanNum = 0;//五元组中的白棋数量
  129. tupleScoreTmp = 0;//五元组得分临时变量
  130. }
  131. }
  132. //2.扫描纵向15行
  133. for(int i = 0; i < 15; i++){
  134. for(int j = 0; j < 11; j++){
  135. int k = j;
  136. while(k < j + 5){
  137. if(chessboard[k][i] == -1) machineChessmanNum++;
  138. else if(chessboard[k][i] == 1)humanChessmanNum++;
  139. k++;
  140. }
  141. tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
  142. //为该五元组的每个位置添加分数
  143. for(k = j; k < j + 5; k++){
  144. score[k][i] += tupleScoreTmp;
  145. }
  146. //置零
  147. humanChessmanNum = 0;//五元组中的黑棋数量
  148. machineChessmanNum = 0;//五元组中的白棋数量
  149. tupleScoreTmp = 0;//五元组得分临时变量
  150. }
  151. }
  152. //3.扫描右上角到左下角上侧部分
  153. for(int i = 14; i >= 4; i--){
  154. for(int k = i, j = 0; j < 15 && k >= 0; j++, k--){
  155. int m = k;
  156. int n = j;
  157. while(m > k - 5 && k - 5 >= -1){
  158. if(chessboard[m][n] == -1) machineChessmanNum++;
  159. else if(chessboard[m][n] == 1)humanChessmanNum++;
  160. m--;
  161. n++;
  162. }
  163. //注意斜向判断的时候,可能构不成五元组(靠近四个角落),遇到这种情况要忽略掉
  164. if(m == k-5){
  165. tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
  166. //为该五元组的每个位置添加分数
  167. m = k;
  168. n = j;
  169. for(; m > k - 5 ; m--, n++){
  170. score[m][n] += tupleScoreTmp;
  171. }
  172. }
  173. //置零
  174. humanChessmanNum = 0;//五元组中的黑棋数量
  175. machineChessmanNum = 0;//五元组中的白棋数量
  176. tupleScoreTmp = 0;//五元组得分临时变量
  177. }
  178. }
  179. //4.扫描右上角到左下角下侧部分
  180. for(int i = 1; i < 15; i++){
  181. for(int k = i, j = 14; j >= 0 && k < 15; j--, k++){
  182. int m = k;
  183. int n = j;
  184. while(m < k + 5 && k + 5 <= 15){
  185. if(chessboard[n][m] == -1) machineChessmanNum++;
  186. else if(chessboard[n][m] == 1)humanChessmanNum++;
  187. m++;
  188. n--;
  189. }
  190. //注意斜向判断的时候,可能构不成五元组(靠近四个角落),遇到这种情况要忽略掉
  191. if(m == k+5){
  192. tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
  193. //为该五元组的每个位置添加分数
  194. m = k;
  195. n = j;
  196. for(; m < k + 5; m++, n--){
  197. score[n][m] += tupleScoreTmp;
  198. }
  199. }
  200. //置零
  201. humanChessmanNum = 0;//五元组中的黑棋数量
  202. machineChessmanNum = 0;//五元组中的白棋数量
  203. tupleScoreTmp = 0;//五元组得分临时变量
  204. }
  205. }
  206. //5.扫描左上角到右下角上侧部分
  207. for(int i = 0; i < 11; i++){
  208. for(int k = i, j = 0; j < 15 && k < 15; j++, k++){
  209. int m = k;
  210. int n = j;
  211. while(m < k + 5 && k + 5 <= 15){
  212. if(chessboard[m][n] == -1) machineChessmanNum++;
  213. else if(chessboard[m][n] == 1)humanChessmanNum++;
  214. m++;
  215. n++;
  216. }
  217. //注意斜向判断的时候,可能构不成五元组(靠近四个角落),遇到这种情况要忽略掉
  218. if(m == k + 5){
  219. tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
  220. //为该五元组的每个位置添加分数
  221. m = k;
  222. n = j;
  223. for(; m < k + 5; m++, n++){
  224. score[m][n] += tupleScoreTmp;
  225. }
  226. }
  227. //置零
  228. humanChessmanNum = 0;//五元组中的黑棋数量
  229. machineChessmanNum = 0;//五元组中的白棋数量
  230. tupleScoreTmp = 0;//五元组得分临时变量
  231. }
  232. }
  233. //6.扫描左上角到右下角下侧部分
  234. for(int i = 1; i < 11; i++){
  235. for(int k = i, j = 0; j < 15 && k < 15; j++, k++){
  236. int m = k;
  237. int n = j;
  238. while(m < k + 5 && k + 5 <= 15){
  239. if(chessboard[n][m] == -1) machineChessmanNum++;
  240. else if(chessboard[n][m] == 1)humanChessmanNum++;
  241. m++;
  242. n++;
  243. }
  244. //注意斜向判断的时候,可能构不成五元组(靠近四个角落),遇到这种情况要忽略掉
  245. if(m == k + 5){
  246. tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);
  247. //为该五元组的每个位置添加分数
  248. m = k;
  249. n = j;
  250. for(; m < k + 5; m++, n++){
  251. score[n][m] += tupleScoreTmp;
  252. }
  253. }
  254. //置零
  255. humanChessmanNum = 0;//五元组中的黑棋数量
  256. machineChessmanNum = 0;//五元组中的白棋数量
  257. tupleScoreTmp = 0;//五元组得分临时变量
  258. }
  259. }
  260. //从空位置中找到得分最大的位置
  261. for(int i = 0; i < 15; i++){
  262. for(int j = 0; j < 15; j++){
  263. if(chessboard[i][j] == 0 && score[i][j] > maxScore){
  264. goalX = i;
  265. goalY = j;
  266. maxScore = score[i][j];
  267. }
  268. }
  269. }
  270. print(maxScore);
  271. if (goalX != -1 && goalY != -1) {
  272. return new Position(goalX.toDouble(), goalY.toDouble(), ChessFlyweightFactory.getInstance().getChess(""));
  273. }
  274. //没找到坐标说明平局了,笔者不处理平局
  275. print("没有找到");
  276. return new Position(-1, -1, ChessFlyweightFactory.getInstance().getChess(""));
  277. }
  278. //各种五元组情况评分表
  279. int tupleScore(int humanChessmanNum, int machineChessmanNum){
  280. //1.既有人类落子,又有机器落子,判分为0
  281. if(humanChessmanNum > 0 && machineChessmanNum > 0){
  282. return 0;
  283. }
  284. //2.全部为空,没有落子,判分为7
  285. if(humanChessmanNum == 0 && machineChessmanNum == 0){
  286. return 7;
  287. }
  288. //3.机器落1子,判分为35
  289. if(machineChessmanNum == 1){
  290. return 35;
  291. }
  292. //4.机器落2子,判分为800
  293. if(machineChessmanNum == 2){
  294. return 800;
  295. }
  296. //5.机器落3子,判分为15000
  297. if(machineChessmanNum == 3){
  298. return 15000;
  299. }
  300. //6.机器落4子,判分为800000
  301. if(machineChessmanNum == 4){
  302. return 800000;
  303. }
  304. //7.人类落1子,判分为15
  305. if(humanChessmanNum == 1){
  306. return 15;
  307. }
  308. //8.人类落2子,判分为400
  309. if(humanChessmanNum == 2){
  310. return 400;
  311. }
  312. //9.人类落3子,判分为1800
  313. if(humanChessmanNum == 3){
  314. return 1800;
  315. }
  316. //10.人类落4子,判分为100000
  317. if(humanChessmanNum == 4){
  318. return 100000;
  319. }
  320. return -1;//若是其他结果肯定出错了。这行代码根本不可能执行
  321. }
  322. }