import 'dart:async'; import 'dart:io'; import 'package:cchess/cchess.dart'; import 'package:fast_gbk/fast_gbk.dart'; import 'package:flutter_chinese_chees/models/engine_type.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: 游戏管理 /// Time : 04/30/2023 Sunday /// Author : liuyuqi.gov@msn.cn 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 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(); final Map> 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 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(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(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 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 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 infos) { return "推荐着法: ${fen.toChineseString(infos[0])}" "${infos.length > 2 ? ' 猜测对方: ${fen.toChineseString(infos[2])}' : ''}"; } String parseInfo(List 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((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 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 getSteps() { return manual.moves.map((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 startEngine() { if (engine != null) { return Future.value(true); } Completer engineFuture = Completer(); 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]; }