import 'dart:async'; import 'dart:isolate'; import 'dart:math'; import 'package:cchess/cchess.dart'; import 'package:cchess_engine/cchess_engine.dart'; import 'package:flutter/foundation.dart'; import '../global.dart'; import '../models/engine_type.dart'; import '../models/game_event.dart'; import '../models/game_setting.dart'; import '../models/engine.dart'; import '../models/player.dart'; import 'player_driver.dart'; class DriverRobot extends PlayerDriver { DriverRobot(Player player) : super(player); late Completer requestMove; bool isCleared = true; @override Future tryDraw() { return Future.value(true); } @override Future move() { requestMove = Completer(); player.manager.add(GameLockEvent(true)); // 网页版用不了引擎 Future.delayed(const Duration(seconds: 1)).then((value) { if (Engine.isSupportEngine && player.manager.setting.robotType == EngineType.elephantEye) { getMoveFromEngine(); } else { // getMove(); getBuiltInMove(); } }); return requestMove.future; } Future getMoveFromEngine() async { player.manager.startEngine().then((v) { if (v) { player.manager.engine! .requestMove(player.manager.fenStr, depth: 10) .then(onEngineMessage); } else { getMove(); } }); } void onEngineMessage(String message) { List parts = message.split(' '); switch (parts[0]) { case 'ucciok': break; case 'nobestmove': case 'isbusy': if (!isCleared) { isCleared = true; return; } if (!requestMove.isCompleted) { player.manager.engine!.removeListener(onEngineMessage); getMove(); } break; case 'bestmove': if (!isCleared) { isCleared = true; return; } player.manager.engine!.removeListener(onEngineMessage); completeMove(parts[1]); break; case 'info': break; case 'id': case 'option': default: return; } } Future getBuiltInMove() async { GameSetting setting = await GameSetting.getInstance(); XQIsoSearch.level = setting.robotLevel; if (kIsWeb) { completeMove( await XQIsoSearch.getMove(IsoMessage(player.manager.fenStr)), ); } else { ReceivePort rPort = ReceivePort(); rPort.listen((message) { completeMove(message); }); Isolate.spawn( XQIsoSearch.getMove, IsoMessage(player.manager.fenStr, rPort.sendPort), ); } } Future getMove() async { logger.info('thinking'); int team = player.team == 'r' ? 0 : 1; List moves = await getAbleMoves(player.manager.fen, team); if (moves.isEmpty) { completeMove('giveup'); return; } //print(moves); await Future.delayed(const Duration(milliseconds: 100)); Map moveGroups = await checkMoves(player.manager.fen, team, moves); //print(moveGroups); await Future.delayed(const Duration(milliseconds: 100)); String move = await pickMove(moveGroups); //print(move); completeMove(move); } /// 获取可以走的着法 Future> getAbleMoves(ChessFen fen, int team) async { List moves = []; List items = fen.getAll(); for (var item in items) { if (item.team == team) { List curMoves = ChessRule(fen) .movePoints(item.position) .map((toPos) => item.position.toCode() + toPos) .toList(); curMoves = curMoves.where((move) { ChessRule rule = ChessRule(fen.copy()); rule.fen.move(move); if (rule.isKingMeet(team)) { return false; } if (rule.isCheck(team)) { return false; } return true; }).toList(); if (curMoves.isNotEmpty) { moves += curMoves; } } } return moves; } /// todo 检查着法优势 吃子(被吃子是否有根以及与本子权重),躲吃,生根,将军,叫杀 将着法按权重分组 Future> checkMoves( ChessFen fen, int team, List moves, ) async { // 着法加分 List weights = [ 49, // 0.将军 199, // 1.叫杀 199, // 2.挡将,挡杀 9, // 3.捉 这四项根据子力价值倍乘 19, // 4.保 19, // 5.吃 9, // 6.躲 0, // 7.闲 进 退 ]; Map moveWeight = {}; ChessRule rule = ChessRule(fen); int enemyTeam = team == 0 ? 1 : 0; // 被将军的局面,生成的都是挡着 if (rule.isCheck(team)) { // 计算挡着后果 for (var move in moves) { ChessRule nRule = ChessRule(fen.copy()); nRule.fen.move(move); // 走着后还能不能被将 bool canCheck = nRule.teamCanCheck(enemyTeam); if (!canCheck) { moveWeight[move] = weights[2]; } else { moveWeight[move] = weights[2] * 3; } } } else { // 获取要被吃的子 List willBeEaten = rule.getBeEatenList(team); for (var move in moves) { moveWeight[move] = 0; ChessPos fromPos = ChessPos.fromCode(move.substring(0, 2)); ChessPos toPos = ChessPos.fromCode(move.substring(2, 4)); String chess = fen[fromPos.y][fromPos.x]; String toChess = fen[toPos.y][toPos.x]; if (toChess != '0') { int toRootCount = rule.rootCount(toPos, enemyTeam); int wPower = rule.getChessWeight(toPos); // 被吃子有根,则要判断双方子力价值才吃 if (toRootCount > 0) { wPower -= rule.getChessWeight(fromPos); } moveWeight[move] = moveWeight[move]! + weights[5] * wPower; } int rootCount = rule.rootCount(fromPos, team); int eRootCount = rule.rootCount(fromPos, enemyTeam); // 躲吃 /*if(rootCount < 1 && eRootCount > 0){ moveWeight[move] += weights[6] * rule.getChessWeight(fromPos); }else if(rootCount < eRootCount){ moveWeight[move] += weights[6] * (rule.getChessWeight(fromPos) - rule.getChessWeight(toPos)); }*/ // 开局兵不挡马路不动兵 int chessCount = rule.fen.getAllChr().length; if (chessCount > 28) { if (chess == 'p') { if (fen[fromPos.y + 1][fromPos.x] == 'n') { moveWeight[move] = moveWeight[move]! + 9; } } else if (chess == 'P') { if (fen[fromPos.y - 1][fromPos.x] == 'N') { moveWeight[move] = moveWeight[move]! + 9; } } // 开局先动马炮 if (['c', 'C', 'n', 'N'].contains(chess)) { moveWeight[move] = moveWeight[move]! + 9; } } if (chessCount > 20) { // 车马炮在原位的优先动 if ((chess == 'C' && fromPos.y == 2 && (fromPos.x == 1 || fromPos.x == 7)) || (chess == 'c' && fromPos.y == 7 && (fromPos.x == 1 || fromPos.x == 7))) { moveWeight[move] = moveWeight[move]! + 19; } if ((chess == 'N' && fromPos.y == 0) || (chess == 'n' && fromPos.y == 9)) { moveWeight[move] = moveWeight[move]! + 19; } if ((chess == 'R' && fromPos.y == 0) || (chess == 'r' && fromPos.y == 9)) { moveWeight[move] = moveWeight[move]! + 9; } } // 马往前跳权重增加 if ((chess == 'n' && toPos.y < fromPos.y) || (chess == 'N' && toPos.y > fromPos.y)) { moveWeight[move] = moveWeight[move]! + 9; } // 马在原位不动车 if ((chess == 'r' && fromPos.y == 9) || (chess == 'R' && fromPos.y == 0)) { ChessPos nPos = rule.fen.find(chess == 'R' ? 'N' : 'n')!; if (fromPos.x == 0) { if (nPos.x == 1 && nPos.y == fromPos.y) { moveWeight[move] = moveWeight[move]! - rule.getChessWeight(nPos); } } else if (fromPos.x == 8) { if (nPos.x == 7 && nPos.y == fromPos.y) { moveWeight[move] = moveWeight[move]! - rule.getChessWeight(nPos); } } } ChessPos ekPos = fen.find(enemyTeam == 0 ? 'K' : 'k')!; // 炮是否应着老将 if (chess == 'c' || chess == 'C') { if (fromPos.y == ekPos.y || fromPos.x == ekPos.x) { if (toPos.y != ekPos.y && toPos.x != ekPos.x) { moveWeight[move] = moveWeight[move]! - weights[0]; } } else { if (toPos.y == ekPos.y || toPos.x == ekPos.x) { moveWeight[move] = moveWeight[move]! + weights[0]; } } } ChessRule mRule = ChessRule(fen.copy()); mRule.fen.move(move); // 走招后要被将军 if (mRule.teamCanCheck(enemyTeam)) { List checkMoves = mRule.getCheckMoves(enemyTeam); //print('将军招法: $checkMoves'); for (var eMove in checkMoves) { ChessRule eRule = ChessRule(mRule.fen.copy()); eRule.fen.move(eMove); // 不能应将,就是杀招 if (eRule.canParryKill(team)) { //print('$move 要被将军'); moveWeight[move] = moveWeight[move]! - weights[0]; } else { logger.info('$move 有杀招'); moveWeight[move] = moveWeight[move]! - weights[1]; } } } else { rootCount = mRule.rootCount(toPos, team); eRootCount = mRule.rootCount(toPos, enemyTeam); for (var bItem in willBeEaten) { // 当前走的子就是被吃的子 if (bItem.position == fromPos) { // 走之后不被吃了 if (eRootCount < 1) { moveWeight[move] = moveWeight[move]! + mRule.getChessWeight(toPos) * weights[6]; } else if (rootCount > 0) { List eItems = mRule.getBeEatList(toPos); moveWeight[move] = moveWeight[move]! + (mRule.getChessWeight(eItems[0].position) - mRule.getChessWeight(toPos)) * weights[6]; } } else { // 不是被吃的子,但是也躲过去了 int oRootCount = mRule.rootCount(bItem.position, enemyTeam); if (oRootCount < 1) { moveWeight[move] = moveWeight[move]! + mRule.getChessWeight(bItem.position) * weights[6]; } else { // 有根了 List eItems = mRule.getBeEatList(bItem.position); moveWeight[move] = moveWeight[move]! + (mRule.getChessWeight(eItems[0].position) - mRule.getChessWeight(bItem.position)) * weights[6]; } } } // 走着后要被吃 if ((rootCount == 0 && eRootCount > 0) || rootCount < eRootCount) { moveWeight[move] = moveWeight[move]! - mRule.getChessWeight(toPos) * weights[5]; } // 捉子优先 List canEatItems = mRule.getEatList(toPos); List oldCanEatItems = rule.getEatList(fromPos); int eatWeight = 0; for (var oItem in oldCanEatItems) { eatWeight += mRule.getChessWeight(oItem.position) * weights[3]; } for (var oItem in canEatItems) { eatWeight -= mRule.getChessWeight(oItem.position) * weights[3]; } moveWeight[move] = moveWeight[move]! - eatWeight; } } } int minWeight = 0; moveWeight.forEach((key, value) { if (minWeight > value) minWeight = value; }); if (minWeight < 0) { moveWeight.updateAll((key, value) => value - minWeight); } logger.info(moveWeight); return moveWeight; } /// todo 从分组好的招法中随机筛选一个 Future pickMove(Map groups) async { int totalSum = 0; for (var wgt in groups.values) { wgt += 1; if (wgt < 0) wgt = 0; totalSum += wgt; } Random random = Random(DateTime.now().millisecondsSinceEpoch); double rand = random.nextDouble() * totalSum; int curSum = 0; String move = ''; for (String key in groups.keys) { move = key; curSum += groups[key]!; if (curSum > rand) { break; } } return move; } @override Future ponder() { throw UnimplementedError(); } @override Future completeMove(String move) async { player.onMove(move).then((value) { requestMove.complete(move); }); } @override Future tryRetract() { throw UnimplementedError(); } }