|
@@ -1,7 +1,546 @@
|
|
|
|
+import 'dart:async';
|
|
|
|
+import 'dart:io';
|
|
|
|
+
|
|
|
|
+import 'package:cchess/cchess.dart';
|
|
|
|
+import 'package:chinese_chess/models/engine_type.dart';
|
|
|
|
+import 'package:fast_gbk/fast_gbk.dart';
|
|
|
|
+
|
|
|
|
+import '../driver/player_driver.dart';
|
|
|
|
+import '../global.dart';
|
|
|
|
+import 'chess_skin.dart';
|
|
|
|
+import 'game_event.dart';
|
|
|
|
+import 'game_setting.dart';
|
|
|
|
+import 'sound.dart';
|
|
|
|
+import 'engine.dart';
|
|
|
|
+import 'player.dart';
|
|
|
|
|
|
/// Description: 游戏管理
|
|
/// Description: 游戏管理
|
|
/// Time : 04/30/2023 Sunday
|
|
/// Time : 04/30/2023 Sunday
|
|
/// Author : liuyuqi.gov@msn.cn
|
|
/// Author : liuyuqi.gov@msn.cn
|
|
class GameManager {
|
|
class GameManager {
|
|
-
|
|
|
|
-}
|
|
|
|
|
|
+ late ChessSkin skin;
|
|
|
|
+ double scale = 1;
|
|
|
|
+
|
|
|
|
+ // 当前对局
|
|
|
|
+ late ChessManual manual;
|
|
|
|
+
|
|
|
|
+ // 算法引擎
|
|
|
|
+ Engine? engine;
|
|
|
|
+ bool engineOK = false;
|
|
|
|
+
|
|
|
|
+ // 是否重新请求招法时的强制stop
|
|
|
|
+ bool isStop = false;
|
|
|
|
+
|
|
|
|
+ // 是否翻转棋盘
|
|
|
|
+ bool _isFlip = false;
|
|
|
|
+ bool get isFlip => _isFlip;
|
|
|
|
+
|
|
|
|
+ void flip() {
|
|
|
|
+ add(GameFlipEvent(!isFlip));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 是否锁定(非玩家操作的时候锁定界面)
|
|
|
|
+ bool _isLock = false;
|
|
|
|
+ bool get isLock => _isLock;
|
|
|
|
+
|
|
|
|
+ // 选手
|
|
|
|
+ List<Player> hands = [];
|
|
|
|
+ int curHand = 0;
|
|
|
|
+
|
|
|
|
+ // 当前着法序号
|
|
|
|
+ int _currentStep = 0;
|
|
|
|
+ int get currentStep => _currentStep;
|
|
|
|
+
|
|
|
|
+ int get stepCount => manual.moveCount;
|
|
|
|
+
|
|
|
|
+ // 是否将军
|
|
|
|
+ bool get isCheckMate => manual.currentMove?.isCheckMate ?? false;
|
|
|
|
+
|
|
|
|
+ // 未吃子着数(半回合数)
|
|
|
|
+ int unEatCount = 0;
|
|
|
|
+
|
|
|
|
+ // 回合数
|
|
|
|
+ int round = 0;
|
|
|
|
+
|
|
|
|
+ final gameEvent = StreamController<GameEvent>();
|
|
|
|
+ final Map<GameEventType, List<void Function(GameEvent)>> listeners = {};
|
|
|
|
+
|
|
|
|
+ // 走子规则
|
|
|
|
+ late ChessRule rule;
|
|
|
|
+
|
|
|
|
+ late GameSetting setting;
|
|
|
|
+
|
|
|
|
+ static GameManager? _instance;
|
|
|
|
+
|
|
|
|
+ static GameManager get instance => _instance ??= GameManager();
|
|
|
|
+
|
|
|
|
+ GameManager._() {
|
|
|
|
+ gameEvent.stream.listen(_onGameEvent);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ factory GameManager() {
|
|
|
|
+ _instance ??= GameManager._();
|
|
|
|
+ return _instance!;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Future<bool> init() async {
|
|
|
|
+ setting = await GameSetting.getInstance();
|
|
|
|
+ manual = ChessManual();
|
|
|
|
+ rule = ChessRule(manual.currentFen);
|
|
|
|
+
|
|
|
|
+ hands.add(Player('r', this, title: manual.red));
|
|
|
|
+ hands.add(Player('b', this, title: manual.black));
|
|
|
|
+ curHand = 0;
|
|
|
|
+ // map = ChessMap.fromFen(ChessManual.startFen);
|
|
|
|
+
|
|
|
|
+ skin = ChessSkin("woods", this);
|
|
|
|
+ skin.readyNotifier.addListener(() {
|
|
|
|
+ add(GameLoadEvent(0));
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void on<T extends GameEvent>(void Function(GameEvent) listener) {
|
|
|
|
+ final type = GameEvent.eventType(T);
|
|
|
|
+ if (type == null) {
|
|
|
|
+ logger.warning('type not match ${T.runtimeType}');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (!listeners.containsKey(type)) {
|
|
|
|
+ listeners[type] = [];
|
|
|
|
+ }
|
|
|
|
+ listeners[type]!.add(listener);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void off<T extends GameEvent>(void Function(GameEvent) listener) {
|
|
|
|
+ final type = GameEvent.eventType(T);
|
|
|
|
+ if (type == null) {
|
|
|
|
+ logger.warning('type not match ${T.runtimeType}');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ listeners[type]?.remove(listener);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void add<T extends GameEvent>(T event) {
|
|
|
|
+ gameEvent.add(event);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void clear() {
|
|
|
|
+ listeners.clear();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void _onGameEvent(GameEvent e) {
|
|
|
|
+ if (e.type == GameEventType.lock) {
|
|
|
|
+ _isLock = e.data;
|
|
|
|
+ }
|
|
|
|
+ if (e.type == GameEventType.flip) {
|
|
|
|
+ _isFlip = e.data;
|
|
|
|
+ }
|
|
|
|
+ if (listeners.containsKey(e.type)) {
|
|
|
|
+ for (var func in listeners[e.type]!) {
|
|
|
|
+ func.call(e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool get canBacktrace => player.canBacktrace;
|
|
|
|
+
|
|
|
|
+ ChessFen get fen => manual.currentFen;
|
|
|
|
+
|
|
|
|
+ /// not last but current
|
|
|
|
+ String get lastMove => manual.currentMove?.move ?? '';
|
|
|
|
+
|
|
|
|
+ void parseMessage(String message) {
|
|
|
|
+ List<String> parts = message.split(' ');
|
|
|
|
+ String instruct = parts.removeAt(0);
|
|
|
|
+ switch (instruct) {
|
|
|
|
+ case 'ucciok':
|
|
|
|
+ engineOK = true;
|
|
|
|
+ add(GameEngineEvent('Engine is OK!'));
|
|
|
|
+ break;
|
|
|
|
+ case 'nobestmove':
|
|
|
|
+ // 强行stop后的nobestmove忽略
|
|
|
|
+ if (isStop) {
|
|
|
|
+ isStop = false;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case 'bestmove':
|
|
|
|
+ logger.info(message);
|
|
|
|
+ message = parseBaseMove(parts);
|
|
|
|
+ break;
|
|
|
|
+ case 'info':
|
|
|
|
+ logger.info(message);
|
|
|
|
+ message = parseInfo(parts);
|
|
|
|
+ break;
|
|
|
|
+ case 'id':
|
|
|
|
+ case 'option':
|
|
|
|
+ default:
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ add(GameEngineEvent(message));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String parseBaseMove(List<String> infos) {
|
|
|
|
+ return "推荐着法: ${fen.toChineseString(infos[0])}"
|
|
|
|
+ "${infos.length > 2 ? ' 猜测对方: ${fen.toChineseString(infos[2])}' : ''}";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String parseInfo(List<String> infos) {
|
|
|
|
+ String first = infos.removeAt(0);
|
|
|
|
+ switch (first) {
|
|
|
|
+ case 'depth':
|
|
|
|
+ String msg = infos[0];
|
|
|
|
+ if (infos.isNotEmpty) {
|
|
|
|
+ String sub = infos.removeAt(0);
|
|
|
|
+ while (sub.isNotEmpty) {
|
|
|
|
+ if (sub == 'score') {
|
|
|
|
+ String score = infos.removeAt(0);
|
|
|
|
+ msg += '(${score.contains('-') ? '' : '+'}$score)';
|
|
|
|
+ } else if (sub == 'pv') {
|
|
|
|
+ msg += fen.toChineseTree(infos).join(' ');
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if (infos.isEmpty) break;
|
|
|
|
+ sub = infos.removeAt(0);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return msg;
|
|
|
|
+ case 'time':
|
|
|
|
+ return '耗时:${infos[0]}(ms)${infos.length > 2 ? ' 节点数 ${infos[2]}' : ''}';
|
|
|
|
+ case 'currmove':
|
|
|
|
+ return '当前招法: ${fen.toChineseString(infos[0])}${infos.length > 2 ? ' ${infos[2]}' : ''}';
|
|
|
|
+ case 'message':
|
|
|
|
+ default:
|
|
|
|
+ return infos.join(' ');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void stop() {
|
|
|
|
+ add(GameLoadEvent(-1));
|
|
|
|
+ isStop = true;
|
|
|
|
+ engine?.stop();
|
|
|
|
+ //currentStep = 0;
|
|
|
|
+
|
|
|
|
+ add(GameLockEvent(true));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void newGame([String fen = ChessManual.startFen]) {
|
|
|
|
+ stop();
|
|
|
|
+
|
|
|
|
+ add(GameStepEvent('clear'));
|
|
|
|
+ add(GameEngineEvent('clear'));
|
|
|
|
+ manual = ChessManual(fen: fen);
|
|
|
|
+ rule = ChessRule(manual.currentFen);
|
|
|
|
+ hands[0].title = manual.red;
|
|
|
|
+ hands[0].driverType = DriverType.user;
|
|
|
|
+ hands[1].title = manual.black;
|
|
|
|
+ hands[1].driverType = DriverType.user;
|
|
|
|
+ curHand = manual.startHand;
|
|
|
|
+
|
|
|
|
+ add(GameLoadEvent(0));
|
|
|
|
+ next();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void loadPGN(String pgn) {
|
|
|
|
+ stop();
|
|
|
|
+
|
|
|
|
+ _loadPGN(pgn);
|
|
|
|
+ add(GameLoadEvent(0));
|
|
|
|
+ next();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool _loadPGN(String pgn) {
|
|
|
|
+ isStop = true;
|
|
|
|
+ engine?.stop();
|
|
|
|
+
|
|
|
|
+ String content = '';
|
|
|
|
+ if (!pgn.contains('\n')) {
|
|
|
|
+ File file = File(pgn);
|
|
|
|
+ if (file.existsSync()) {
|
|
|
|
+ //content = file.readAsStringSync(encoding: Encoding.getByName('gbk'));
|
|
|
|
+ content = gbk.decode(file.readAsBytesSync());
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ content = pgn;
|
|
|
|
+ }
|
|
|
|
+ manual = ChessManual.load(content);
|
|
|
|
+ hands[0].title = manual.red;
|
|
|
|
+ hands[1].title = manual.black;
|
|
|
|
+
|
|
|
|
+ add(GameLoadEvent(0));
|
|
|
|
+ // 加载步数
|
|
|
|
+ if (manual.moveCount > 0) {
|
|
|
|
+ // print(manual.moves);
|
|
|
|
+ add(
|
|
|
|
+ GameStepEvent(
|
|
|
|
+ manual.moves.map<String>((e) => e.toChineseString()).join('\n'),
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ manual.loadHistory(-1);
|
|
|
|
+ rule.fen = manual.currentFen;
|
|
|
|
+ add(GameStepEvent('step'));
|
|
|
|
+
|
|
|
|
+ curHand = manual.startHand;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void loadFen(String fen) {
|
|
|
|
+ newGame(fen);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 重载历史局面
|
|
|
|
+ void loadHistory(int index) {
|
|
|
|
+ if (index >= manual.moveCount) {
|
|
|
|
+ logger.info('History error');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (index == _currentStep) {
|
|
|
|
+ logger.info('History no change');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ _currentStep = index;
|
|
|
|
+ manual.loadHistory(index);
|
|
|
|
+ rule.fen = manual.currentFen;
|
|
|
|
+ curHand = (_currentStep + 1) % 2;
|
|
|
|
+ add(GamePlayerEvent(curHand));
|
|
|
|
+ add(GameLoadEvent(_currentStep + 1));
|
|
|
|
+
|
|
|
|
+ logger.info('history $_currentStep');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// 切换驱动
|
|
|
|
+ void switchDriver(int team, DriverType driverType) {
|
|
|
|
+ hands[team].driverType = driverType;
|
|
|
|
+ if (team == curHand && driverType == DriverType.robot) {
|
|
|
|
+ //add(GameLockEvent(true));
|
|
|
|
+ next();
|
|
|
|
+ } else if (driverType == DriverType.user) {
|
|
|
|
+ //add(GameLockEvent(false));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// 调用对应的玩家开始下一步
|
|
|
|
+ Future<void> next() async {
|
|
|
|
+ final move = await player.move();
|
|
|
|
+ if (move == null) return;
|
|
|
|
+
|
|
|
|
+ addMove(move);
|
|
|
|
+ final canNext = checkResult(curHand == 0 ? 1 : 0, _currentStep - 1);
|
|
|
|
+ logger.info('canNext $canNext');
|
|
|
|
+ if (canNext) {
|
|
|
|
+ switchPlayer();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// 从用户落着 TODO 检查出发点是否有子,检查落点是否对方子
|
|
|
|
+ void addStep(ChessPos from, ChessPos next) async {
|
|
|
|
+ player.completeMove('${from.toCode()}${next.toCode()}');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void addMove(String move) {
|
|
|
|
+ logger.info('addmove $move');
|
|
|
|
+ if (PlayerDriver.isAction(move)) {
|
|
|
|
+ if (move == PlayerDriver.rstGiveUp) {
|
|
|
|
+ setResult(
|
|
|
|
+ curHand == 0 ? ChessManual.resultFstLoose : ChessManual.resultFstWin,
|
|
|
|
+ '${player.title}认输',
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ if (move == PlayerDriver.rstDraw) {
|
|
|
|
+ setResult(ChessManual.resultFstDraw);
|
|
|
|
+ }
|
|
|
|
+ if (move == PlayerDriver.rstRetract) {
|
|
|
|
+ // todo 悔棋
|
|
|
|
+ }
|
|
|
|
+ if (move.contains(PlayerDriver.rstRqstDraw)) {
|
|
|
|
+ move = move.replaceAll(PlayerDriver.rstRqstDraw, '').trim();
|
|
|
|
+ if (move.isEmpty) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!ChessManual.isPosMove(move)) {
|
|
|
|
+ logger.info('着法错误 $move');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 如果当前不是最后一步,移除后面着法
|
|
|
|
+ if (!manual.isLast) {
|
|
|
|
+ add(GameLoadEvent(-2));
|
|
|
|
+ add(GameStepEvent('clear'));
|
|
|
|
+ manual.addMove(move, addStep: _currentStep);
|
|
|
|
+ } else {
|
|
|
|
+ add(GameLoadEvent(-2));
|
|
|
|
+ manual.addMove(move);
|
|
|
|
+ }
|
|
|
|
+ _currentStep = manual.currentStep;
|
|
|
|
+
|
|
|
|
+ final curMove = manual.currentMove!;
|
|
|
|
+
|
|
|
|
+ if (curMove.isCheckMate) {
|
|
|
|
+ unEatCount++;
|
|
|
|
+ Sound.play(Sound.move);
|
|
|
|
+ } else if (curMove.isEat) {
|
|
|
|
+ unEatCount = 0;
|
|
|
|
+ Sound.play(Sound.capture);
|
|
|
|
+ } else {
|
|
|
|
+ unEatCount++;
|
|
|
|
+ Sound.play(Sound.move);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ add(GameStepEvent(curMove.toChineseString()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void setResult(String result, [String description = '']) {
|
|
|
|
+ if (!ChessManual.results.contains(result)) {
|
|
|
|
+ logger.info('结果不合法 $result');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ logger.info('本局结果:$result');
|
|
|
|
+ add(GameResultEvent('$result $description'));
|
|
|
|
+ if (result == ChessManual.resultFstDraw) {
|
|
|
|
+ Sound.play(Sound.draw);
|
|
|
|
+ } else if (result == ChessManual.resultFstWin) {
|
|
|
|
+ Sound.play(Sound.win);
|
|
|
|
+ } else if (result == ChessManual.resultFstLoose) {
|
|
|
|
+ Sound.play(Sound.loose);
|
|
|
|
+ }
|
|
|
|
+ manual.result = result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// 棋局结果判断
|
|
|
|
+ bool checkResult(int hand, int curMove) {
|
|
|
|
+ logger.info('checkResult');
|
|
|
|
+
|
|
|
|
+ int repeatRound = manual.repeatRound();
|
|
|
|
+ if (repeatRound > 2) {
|
|
|
|
+ // TODO 提醒
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 判断和棋
|
|
|
|
+ if (unEatCount >= 120) {
|
|
|
|
+ setResult(ChessManual.resultFstDraw, '60回合无吃子判和');
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //isCheckMate = rule.isCheck(hand);
|
|
|
|
+ final moveStep = manual.currentMove!;
|
|
|
|
+ logger.info('是否将军 ${moveStep.isCheckMate}');
|
|
|
|
+
|
|
|
|
+ // 判断输赢,包括能否应将,长将
|
|
|
|
+ if (moveStep.isCheckMate) {
|
|
|
|
+ //manual.moves[curMove].isCheckMate = isCheckMate;
|
|
|
|
+
|
|
|
|
+ if (rule.canParryKill(hand)) {
|
|
|
|
+ // 长将
|
|
|
|
+ if (repeatRound > 3) {
|
|
|
|
+ setResult(
|
|
|
|
+ hand == 0 ? ChessManual.resultFstLoose : ChessManual.resultFstWin,
|
|
|
|
+ '不变招长将作负',
|
|
|
|
+ );
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ Sound.play(Sound.check);
|
|
|
|
+ add(GameResultEvent('checkMate'));
|
|
|
|
+ } else {
|
|
|
|
+ setResult(
|
|
|
|
+ hand == 0 ? ChessManual.resultFstLoose : ChessManual.resultFstWin,
|
|
|
|
+ '绝杀',
|
|
|
|
+ );
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if (rule.isTrapped(hand)) {
|
|
|
|
+ setResult(
|
|
|
|
+ hand == 0 ? ChessManual.resultFstLoose : ChessManual.resultFstWin,
|
|
|
|
+ '困毙',
|
|
|
|
+ );
|
|
|
|
+ return false;
|
|
|
|
+ } else if (moveStep.isEat) {
|
|
|
|
+ add(GameResultEvent('eat'));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // TODO 判断长捉,一捉一将,一将一杀
|
|
|
|
+ if (repeatRound > 3) {
|
|
|
|
+ setResult(ChessManual.resultFstDraw, '不变招判和');
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ List<String> getSteps() {
|
|
|
|
+ return manual.moves.map<String>((cs) => cs.toChineseString()).toList();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void dispose() {
|
|
|
|
+ if (engine != null) {
|
|
|
|
+ engine?.stop();
|
|
|
|
+ engine?.quit();
|
|
|
|
+ engine = null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void switchPlayer() {
|
|
|
|
+ curHand++;
|
|
|
|
+ if (curHand >= hands.length) {
|
|
|
|
+ curHand = 0;
|
|
|
|
+ }
|
|
|
|
+ add(GamePlayerEvent(curHand));
|
|
|
|
+
|
|
|
|
+ logger.info('切换选手:${player.title} ${player.team} ${player.driver}');
|
|
|
|
+
|
|
|
|
+ logger.info(player.title);
|
|
|
|
+ next();
|
|
|
|
+ add(GameEngineEvent('clear'));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Future<bool> startEngine() {
|
|
|
|
+ if (engine != null) {
|
|
|
|
+ return Future.value(true);
|
|
|
|
+ }
|
|
|
|
+ Completer<bool> engineFuture = Completer<bool>();
|
|
|
|
+ engine = Engine(EngineType.pikafish);
|
|
|
|
+ engineOK = false;
|
|
|
|
+ engine?.init().then((Process? v) {
|
|
|
|
+ engineOK = true;
|
|
|
|
+ engine?.addListener(parseMessage);
|
|
|
|
+ engineFuture.complete(true);
|
|
|
|
+ });
|
|
|
|
+ return engineFuture.future;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void requestHelp() {
|
|
|
|
+ if (engine == null) {
|
|
|
|
+ startEngine().then((v) {
|
|
|
|
+ if (v) {
|
|
|
|
+ requestHelp();
|
|
|
|
+ } else {
|
|
|
|
+ logger.info('engine is not support');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ if (engineOK) {
|
|
|
|
+ isStop = true;
|
|
|
|
+ engine?.stop();
|
|
|
|
+ engine?.position(fenStr);
|
|
|
|
+ engine?.go(depth: 10);
|
|
|
|
+ } else {
|
|
|
|
+ logger.info('engine is not ok');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String get fenStr => '${manual.currentFen.fen} ${curHand > 0 ? 'b' : 'w'}'
|
|
|
|
+ ' - - $unEatCount ${manual.moveCount ~/ 2}';
|
|
|
|
+
|
|
|
|
+ Player get player => hands[curHand];
|
|
|
|
+
|
|
|
|
+ Player getPlayer(int hand) => hands[hand];
|
|
|
|
+}
|