driver_robot.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. import 'dart:async';
  2. import 'dart:isolate';
  3. import 'dart:math';
  4. import 'package:cchess/cchess.dart';
  5. import 'package:cchess_engine/cchess_engine.dart';
  6. import 'package:flutter/foundation.dart';
  7. import '../global.dart';
  8. import '../models/engine_type.dart';
  9. import '../models/game_event.dart';
  10. import '../models/game_setting.dart';
  11. import '../models/engine.dart';
  12. import '../models/player.dart';
  13. import 'player_driver.dart';
  14. class DriverRobot extends PlayerDriver {
  15. DriverRobot(Player player) : super(player);
  16. late Completer<String> requestMove;
  17. bool isCleared = true;
  18. @override
  19. Future<bool> tryDraw() {
  20. return Future.value(true);
  21. }
  22. @override
  23. Future<String?> move() {
  24. requestMove = Completer<String>();
  25. player.manager.add(GameLockEvent(true));
  26. // 网页版用不了引擎
  27. Future.delayed(const Duration(seconds: 1)).then((value) {
  28. if (Engine.isSupportEngine &&
  29. player.manager.setting.robotType == EngineType.elephantEye) {
  30. getMoveFromEngine();
  31. } else {
  32. // getMove();
  33. getBuiltInMove();
  34. }
  35. });
  36. return requestMove.future;
  37. }
  38. Future<void> getMoveFromEngine() async {
  39. player.manager.startEngine().then((v) {
  40. if (v) {
  41. player.manager.engine!
  42. .requestMove(player.manager.fenStr, depth: 10)
  43. .then(onEngineMessage);
  44. } else {
  45. getMove();
  46. }
  47. });
  48. }
  49. void onEngineMessage(String message) {
  50. List<String> parts = message.split(' ');
  51. switch (parts[0]) {
  52. case 'ucciok':
  53. break;
  54. case 'nobestmove':
  55. case 'isbusy':
  56. if (!isCleared) {
  57. isCleared = true;
  58. return;
  59. }
  60. if (!requestMove.isCompleted) {
  61. player.manager.engine!.removeListener(onEngineMessage);
  62. getMove();
  63. }
  64. break;
  65. case 'bestmove':
  66. if (!isCleared) {
  67. isCleared = true;
  68. return;
  69. }
  70. player.manager.engine!.removeListener(onEngineMessage);
  71. completeMove(parts[1]);
  72. break;
  73. case 'info':
  74. break;
  75. case 'id':
  76. case 'option':
  77. default:
  78. return;
  79. }
  80. }
  81. Future<void> getBuiltInMove() async {
  82. GameSetting setting = await GameSetting.getInstance();
  83. XQIsoSearch.level = setting.robotLevel;
  84. if (kIsWeb) {
  85. completeMove(
  86. await XQIsoSearch.getMove(IsoMessage(player.manager.fenStr)),
  87. );
  88. } else {
  89. ReceivePort rPort = ReceivePort();
  90. rPort.listen((message) {
  91. completeMove(message);
  92. });
  93. Isolate.spawn<IsoMessage>(
  94. XQIsoSearch.getMove,
  95. IsoMessage(player.manager.fenStr, rPort.sendPort),
  96. );
  97. }
  98. }
  99. Future<void> getMove() async {
  100. logger.info('thinking');
  101. int team = player.team == 'r' ? 0 : 1;
  102. List<String> moves = await getAbleMoves(player.manager.fen, team);
  103. if (moves.isEmpty) {
  104. completeMove('giveup');
  105. return;
  106. }
  107. //print(moves);
  108. await Future.delayed(const Duration(milliseconds: 100));
  109. Map<String, int> moveGroups =
  110. await checkMoves(player.manager.fen, team, moves);
  111. //print(moveGroups);
  112. await Future.delayed(const Duration(milliseconds: 100));
  113. String move = await pickMove(moveGroups);
  114. //print(move);
  115. completeMove(move);
  116. }
  117. /// 获取可以走的着法
  118. Future<List<String>> getAbleMoves(ChessFen fen, int team) async {
  119. List<String> moves = [];
  120. List<ChessItem> items = fen.getAll();
  121. for (var item in items) {
  122. if (item.team == team) {
  123. List<String> curMoves = ChessRule(fen)
  124. .movePoints(item.position)
  125. .map<String>((toPos) => item.position.toCode() + toPos)
  126. .toList();
  127. curMoves = curMoves.where((move) {
  128. ChessRule rule = ChessRule(fen.copy());
  129. rule.fen.move(move);
  130. if (rule.isKingMeet(team)) {
  131. return false;
  132. }
  133. if (rule.isCheck(team)) {
  134. return false;
  135. }
  136. return true;
  137. }).toList();
  138. if (curMoves.isNotEmpty) {
  139. moves += curMoves;
  140. }
  141. }
  142. }
  143. return moves;
  144. }
  145. /// todo 检查着法优势 吃子(被吃子是否有根以及与本子权重),躲吃,生根,将军,叫杀 将着法按权重分组
  146. Future<Map<String, int>> checkMoves(
  147. ChessFen fen,
  148. int team,
  149. List<String> moves,
  150. ) async {
  151. // 着法加分
  152. List<int> weights = [
  153. 49, // 0.将军
  154. 199, // 1.叫杀
  155. 199, // 2.挡将,挡杀
  156. 9, // 3.捉 这四项根据子力价值倍乘
  157. 19, // 4.保
  158. 19, // 5.吃
  159. 9, // 6.躲
  160. 0, // 7.闲 进 退
  161. ];
  162. Map<String, int> moveWeight = {};
  163. ChessRule rule = ChessRule(fen);
  164. int enemyTeam = team == 0 ? 1 : 0;
  165. // 被将军的局面,生成的都是挡着
  166. if (rule.isCheck(team)) {
  167. // 计算挡着后果
  168. for (var move in moves) {
  169. ChessRule nRule = ChessRule(fen.copy());
  170. nRule.fen.move(move);
  171. // 走着后还能不能被将
  172. bool canCheck = nRule.teamCanCheck(enemyTeam);
  173. if (!canCheck) {
  174. moveWeight[move] = weights[2];
  175. } else {
  176. moveWeight[move] = weights[2] * 3;
  177. }
  178. }
  179. } else {
  180. // 获取要被吃的子
  181. List<ChessItem> willBeEaten = rule.getBeEatenList(team);
  182. for (var move in moves) {
  183. moveWeight[move] = 0;
  184. ChessPos fromPos = ChessPos.fromCode(move.substring(0, 2));
  185. ChessPos toPos = ChessPos.fromCode(move.substring(2, 4));
  186. String chess = fen[fromPos.y][fromPos.x];
  187. String toChess = fen[toPos.y][toPos.x];
  188. if (toChess != '0') {
  189. int toRootCount = rule.rootCount(toPos, enemyTeam);
  190. int wPower = rule.getChessWeight(toPos);
  191. // 被吃子有根,则要判断双方子力价值才吃
  192. if (toRootCount > 0) {
  193. wPower -= rule.getChessWeight(fromPos);
  194. }
  195. moveWeight[move] = moveWeight[move]! + weights[5] * wPower;
  196. }
  197. int rootCount = rule.rootCount(fromPos, team);
  198. int eRootCount = rule.rootCount(fromPos, enemyTeam);
  199. // 躲吃
  200. /*if(rootCount < 1 && eRootCount > 0){
  201. moveWeight[move] += weights[6] * rule.getChessWeight(fromPos);
  202. }else if(rootCount < eRootCount){
  203. moveWeight[move] += weights[6] * (rule.getChessWeight(fromPos) - rule.getChessWeight(toPos));
  204. }*/
  205. // 开局兵不挡马路不动兵
  206. int chessCount = rule.fen.getAllChr().length;
  207. if (chessCount > 28) {
  208. if (chess == 'p') {
  209. if (fen[fromPos.y + 1][fromPos.x] == 'n') {
  210. moveWeight[move] = moveWeight[move]! + 9;
  211. }
  212. } else if (chess == 'P') {
  213. if (fen[fromPos.y - 1][fromPos.x] == 'N') {
  214. moveWeight[move] = moveWeight[move]! + 9;
  215. }
  216. }
  217. // 开局先动马炮
  218. if (['c', 'C', 'n', 'N'].contains(chess)) {
  219. moveWeight[move] = moveWeight[move]! + 9;
  220. }
  221. }
  222. if (chessCount > 20) {
  223. // 车马炮在原位的优先动
  224. if ((chess == 'C' &&
  225. fromPos.y == 2 &&
  226. (fromPos.x == 1 || fromPos.x == 7)) ||
  227. (chess == 'c' &&
  228. fromPos.y == 7 &&
  229. (fromPos.x == 1 || fromPos.x == 7))) {
  230. moveWeight[move] = moveWeight[move]! + 19;
  231. }
  232. if ((chess == 'N' && fromPos.y == 0) ||
  233. (chess == 'n' && fromPos.y == 9)) {
  234. moveWeight[move] = moveWeight[move]! + 19;
  235. }
  236. if ((chess == 'R' && fromPos.y == 0) ||
  237. (chess == 'r' && fromPos.y == 9)) {
  238. moveWeight[move] = moveWeight[move]! + 9;
  239. }
  240. }
  241. // 马往前跳权重增加
  242. if ((chess == 'n' && toPos.y < fromPos.y) ||
  243. (chess == 'N' && toPos.y > fromPos.y)) {
  244. moveWeight[move] = moveWeight[move]! + 9;
  245. }
  246. // 马在原位不动车
  247. if ((chess == 'r' && fromPos.y == 9) ||
  248. (chess == 'R' && fromPos.y == 0)) {
  249. ChessPos nPos = rule.fen.find(chess == 'R' ? 'N' : 'n')!;
  250. if (fromPos.x == 0) {
  251. if (nPos.x == 1 && nPos.y == fromPos.y) {
  252. moveWeight[move] = moveWeight[move]! - rule.getChessWeight(nPos);
  253. }
  254. } else if (fromPos.x == 8) {
  255. if (nPos.x == 7 && nPos.y == fromPos.y) {
  256. moveWeight[move] = moveWeight[move]! - rule.getChessWeight(nPos);
  257. }
  258. }
  259. }
  260. ChessPos ekPos = fen.find(enemyTeam == 0 ? 'K' : 'k')!;
  261. // 炮是否应着老将
  262. if (chess == 'c' || chess == 'C') {
  263. if (fromPos.y == ekPos.y || fromPos.x == ekPos.x) {
  264. if (toPos.y != ekPos.y && toPos.x != ekPos.x) {
  265. moveWeight[move] = moveWeight[move]! - weights[0];
  266. }
  267. } else {
  268. if (toPos.y == ekPos.y || toPos.x == ekPos.x) {
  269. moveWeight[move] = moveWeight[move]! + weights[0];
  270. }
  271. }
  272. }
  273. ChessRule mRule = ChessRule(fen.copy());
  274. mRule.fen.move(move);
  275. // 走招后要被将军
  276. if (mRule.teamCanCheck(enemyTeam)) {
  277. List<String> checkMoves = mRule.getCheckMoves(enemyTeam);
  278. //print('将军招法: $checkMoves');
  279. for (var eMove in checkMoves) {
  280. ChessRule eRule = ChessRule(mRule.fen.copy());
  281. eRule.fen.move(eMove);
  282. // 不能应将,就是杀招
  283. if (eRule.canParryKill(team)) {
  284. //print('$move 要被将军');
  285. moveWeight[move] = moveWeight[move]! - weights[0];
  286. } else {
  287. logger.info('$move 有杀招');
  288. moveWeight[move] = moveWeight[move]! - weights[1];
  289. }
  290. }
  291. } else {
  292. rootCount = mRule.rootCount(toPos, team);
  293. eRootCount = mRule.rootCount(toPos, enemyTeam);
  294. for (var bItem in willBeEaten) {
  295. // 当前走的子就是被吃的子
  296. if (bItem.position == fromPos) {
  297. // 走之后不被吃了
  298. if (eRootCount < 1) {
  299. moveWeight[move] = moveWeight[move]! +
  300. mRule.getChessWeight(toPos) * weights[6];
  301. } else if (rootCount > 0) {
  302. List<ChessItem> eItems = mRule.getBeEatList(toPos);
  303. moveWeight[move] = moveWeight[move]! +
  304. (mRule.getChessWeight(eItems[0].position) -
  305. mRule.getChessWeight(toPos)) *
  306. weights[6];
  307. }
  308. } else {
  309. // 不是被吃的子,但是也躲过去了
  310. int oRootCount = mRule.rootCount(bItem.position, enemyTeam);
  311. if (oRootCount < 1) {
  312. moveWeight[move] = moveWeight[move]! +
  313. mRule.getChessWeight(bItem.position) * weights[6];
  314. } else {
  315. // 有根了
  316. List<ChessItem> eItems = mRule.getBeEatList(bItem.position);
  317. moveWeight[move] = moveWeight[move]! +
  318. (mRule.getChessWeight(eItems[0].position) -
  319. mRule.getChessWeight(bItem.position)) *
  320. weights[6];
  321. }
  322. }
  323. }
  324. // 走着后要被吃
  325. if ((rootCount == 0 && eRootCount > 0) || rootCount < eRootCount) {
  326. moveWeight[move] =
  327. moveWeight[move]! - mRule.getChessWeight(toPos) * weights[5];
  328. }
  329. // 捉子优先
  330. List<ChessItem> canEatItems = mRule.getEatList(toPos);
  331. List<ChessItem> oldCanEatItems = rule.getEatList(fromPos);
  332. int eatWeight = 0;
  333. for (var oItem in oldCanEatItems) {
  334. eatWeight += mRule.getChessWeight(oItem.position) * weights[3];
  335. }
  336. for (var oItem in canEatItems) {
  337. eatWeight -= mRule.getChessWeight(oItem.position) * weights[3];
  338. }
  339. moveWeight[move] = moveWeight[move]! - eatWeight;
  340. }
  341. }
  342. }
  343. int minWeight = 0;
  344. moveWeight.forEach((key, value) {
  345. if (minWeight > value) minWeight = value;
  346. });
  347. if (minWeight < 0) {
  348. moveWeight.updateAll((key, value) => value - minWeight);
  349. }
  350. logger.info(moveWeight);
  351. return moveWeight;
  352. }
  353. /// todo 从分组好的招法中随机筛选一个
  354. Future<String> pickMove(Map<String, int> groups) async {
  355. int totalSum = 0;
  356. for (var wgt in groups.values) {
  357. wgt += 1;
  358. if (wgt < 0) wgt = 0;
  359. totalSum += wgt;
  360. }
  361. Random random = Random(DateTime.now().millisecondsSinceEpoch);
  362. double rand = random.nextDouble() * totalSum;
  363. int curSum = 0;
  364. String move = '';
  365. for (String key in groups.keys) {
  366. move = key;
  367. curSum += groups[key]!;
  368. if (curSum > rand) {
  369. break;
  370. }
  371. }
  372. return move;
  373. }
  374. @override
  375. Future<String> ponder() {
  376. throw UnimplementedError();
  377. }
  378. @override
  379. Future<void> completeMove(String move) async {
  380. player.onMove(move).then((value) {
  381. requestMove.complete(move);
  382. });
  383. }
  384. @override
  385. Future<bool> tryRetract() {
  386. throw UnimplementedError();
  387. }
  388. }