Ai.dart 12 KB

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