game_manager.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:cchess/cchess.dart';
  4. import 'package:fast_gbk/fast_gbk.dart';
  5. import 'package:flutter_chinese_chees/models/engine_type.dart';
  6. import '../driver/player_driver.dart';
  7. import '../global.dart';
  8. import 'chess_skin.dart';
  9. import 'game_event.dart';
  10. import 'game_setting.dart';
  11. import 'sound.dart';
  12. import 'engine.dart';
  13. import 'player.dart';
  14. /// Description: 游戏管理
  15. /// Time : 04/30/2023 Sunday
  16. /// Author : liuyuqi.gov@msn.cn
  17. class GameManager {
  18. late ChessSkin skin;
  19. double scale = 1;
  20. // 当前对局
  21. late ChessManual manual;
  22. // 算法引擎
  23. Engine? engine;
  24. bool engineOK = false;
  25. // 是否重新请求招法时的强制stop
  26. bool isStop = false;
  27. // 是否翻转棋盘
  28. bool _isFlip = false;
  29. bool get isFlip => _isFlip;
  30. void flip() {
  31. add(GameFlipEvent(!isFlip));
  32. }
  33. // 是否锁定(非玩家操作的时候锁定界面)
  34. bool _isLock = false;
  35. bool get isLock => _isLock;
  36. // 选手
  37. List<Player> hands = [];
  38. int curHand = 0;
  39. // 当前着法序号
  40. int _currentStep = 0;
  41. int get currentStep => _currentStep;
  42. int get stepCount => manual.moveCount;
  43. // 是否将军
  44. bool get isCheckMate => manual.currentMove?.isCheckMate ?? false;
  45. // 未吃子着数(半回合数)
  46. int unEatCount = 0;
  47. // 回合数
  48. int round = 0;
  49. final gameEvent = StreamController<GameEvent>();
  50. final Map<GameEventType, List<void Function(GameEvent)>> listeners = {};
  51. // 走子规则
  52. late ChessRule rule;
  53. late GameSetting setting;
  54. static GameManager? _instance;
  55. static GameManager get instance => _instance ??= GameManager();
  56. GameManager._() {
  57. gameEvent.stream.listen(_onGameEvent);
  58. }
  59. factory GameManager() {
  60. _instance ??= GameManager._();
  61. return _instance!;
  62. }
  63. Future<bool> init() async {
  64. setting = await GameSetting.getInstance();
  65. manual = ChessManual();
  66. rule = ChessRule(manual.currentFen);
  67. hands.add(Player('r', this, title: manual.red));
  68. hands.add(Player('b', this, title: manual.black));
  69. curHand = 0;
  70. // map = ChessMap.fromFen(ChessManual.startFen);
  71. skin = ChessSkin("woods", this);
  72. skin.readyNotifier.addListener(() {
  73. add(GameLoadEvent(0));
  74. });
  75. return true;
  76. }
  77. void on<T extends GameEvent>(void Function(GameEvent) listener) {
  78. final type = GameEvent.eventType(T);
  79. if (type == null) {
  80. logger.warning('type not match ${T.runtimeType}');
  81. return;
  82. }
  83. if (!listeners.containsKey(type)) {
  84. listeners[type] = [];
  85. }
  86. listeners[type]!.add(listener);
  87. }
  88. void off<T extends GameEvent>(void Function(GameEvent) listener) {
  89. final type = GameEvent.eventType(T);
  90. if (type == null) {
  91. logger.warning('type not match ${T.runtimeType}');
  92. return;
  93. }
  94. listeners[type]?.remove(listener);
  95. }
  96. void add<T extends GameEvent>(T event) {
  97. gameEvent.add(event);
  98. }
  99. void clear() {
  100. listeners.clear();
  101. }
  102. void _onGameEvent(GameEvent e) {
  103. if (e.type == GameEventType.lock) {
  104. _isLock = e.data;
  105. }
  106. if (e.type == GameEventType.flip) {
  107. _isFlip = e.data;
  108. }
  109. if (listeners.containsKey(e.type)) {
  110. for (var func in listeners[e.type]!) {
  111. func.call(e);
  112. }
  113. }
  114. }
  115. bool get canBacktrace => player.canBacktrace;
  116. ChessFen get fen => manual.currentFen;
  117. /// not last but current
  118. String get lastMove => manual.currentMove?.move ?? '';
  119. void parseMessage(String message) {
  120. List<String> parts = message.split(' ');
  121. String instruct = parts.removeAt(0);
  122. switch (instruct) {
  123. case 'ucciok':
  124. engineOK = true;
  125. add(GameEngineEvent('Engine is OK!'));
  126. break;
  127. case 'nobestmove':
  128. // 强行stop后的nobestmove忽略
  129. if (isStop) {
  130. isStop = false;
  131. return;
  132. }
  133. break;
  134. case 'bestmove':
  135. logger.info(message);
  136. message = parseBaseMove(parts);
  137. break;
  138. case 'info':
  139. logger.info(message);
  140. message = parseInfo(parts);
  141. break;
  142. case 'id':
  143. case 'option':
  144. default:
  145. return;
  146. }
  147. add(GameEngineEvent(message));
  148. }
  149. String parseBaseMove(List<String> infos) {
  150. return "推荐着法: ${fen.toChineseString(infos[0])}"
  151. "${infos.length > 2 ? ' 猜测对方: ${fen.toChineseString(infos[2])}' : ''}";
  152. }
  153. String parseInfo(List<String> infos) {
  154. String first = infos.removeAt(0);
  155. switch (first) {
  156. case 'depth':
  157. String msg = infos[0];
  158. if (infos.isNotEmpty) {
  159. String sub = infos.removeAt(0);
  160. while (sub.isNotEmpty) {
  161. if (sub == 'score') {
  162. String score = infos.removeAt(0);
  163. msg += '(${score.contains('-') ? '' : '+'}$score)';
  164. } else if (sub == 'pv') {
  165. msg += fen.toChineseTree(infos).join(' ');
  166. break;
  167. }
  168. if (infos.isEmpty) break;
  169. sub = infos.removeAt(0);
  170. }
  171. }
  172. return msg;
  173. case 'time':
  174. return '耗时:${infos[0]}(ms)${infos.length > 2 ? ' 节点数 ${infos[2]}' : ''}';
  175. case 'currmove':
  176. return '当前招法: ${fen.toChineseString(infos[0])}${infos.length > 2 ? ' ${infos[2]}' : ''}';
  177. case 'message':
  178. default:
  179. return infos.join(' ');
  180. }
  181. }
  182. void stop() {
  183. add(GameLoadEvent(-1));
  184. isStop = true;
  185. engine?.stop();
  186. //currentStep = 0;
  187. add(GameLockEvent(true));
  188. }
  189. void newGame([String fen = ChessManual.startFen]) {
  190. stop();
  191. add(GameStepEvent('clear'));
  192. add(GameEngineEvent('clear'));
  193. manual = ChessManual(fen: fen);
  194. rule = ChessRule(manual.currentFen);
  195. hands[0].title = manual.red;
  196. hands[0].driverType = DriverType.user;
  197. hands[1].title = manual.black;
  198. hands[1].driverType = DriverType.user;
  199. curHand = manual.startHand;
  200. add(GameLoadEvent(0));
  201. next();
  202. }
  203. void loadPGN(String pgn) {
  204. stop();
  205. _loadPGN(pgn);
  206. add(GameLoadEvent(0));
  207. next();
  208. }
  209. bool _loadPGN(String pgn) {
  210. isStop = true;
  211. engine?.stop();
  212. String content = '';
  213. if (!pgn.contains('\n')) {
  214. File file = File(pgn);
  215. if (file.existsSync()) {
  216. //content = file.readAsStringSync(encoding: Encoding.getByName('gbk'));
  217. content = gbk.decode(file.readAsBytesSync());
  218. }
  219. } else {
  220. content = pgn;
  221. }
  222. manual = ChessManual.load(content);
  223. hands[0].title = manual.red;
  224. hands[1].title = manual.black;
  225. add(GameLoadEvent(0));
  226. // 加载步数
  227. if (manual.moveCount > 0) {
  228. // print(manual.moves);
  229. add(
  230. GameStepEvent(
  231. manual.moves.map<String>((e) => e.toChineseString()).join('\n'),
  232. ),
  233. );
  234. }
  235. manual.loadHistory(-1);
  236. rule.fen = manual.currentFen;
  237. add(GameStepEvent('step'));
  238. curHand = manual.startHand;
  239. return true;
  240. }
  241. void loadFen(String fen) {
  242. newGame(fen);
  243. }
  244. // 重载历史局面
  245. void loadHistory(int index) {
  246. if (index >= manual.moveCount) {
  247. logger.info('History error');
  248. return;
  249. }
  250. if (index == _currentStep) {
  251. logger.info('History no change');
  252. return;
  253. }
  254. _currentStep = index;
  255. manual.loadHistory(index);
  256. rule.fen = manual.currentFen;
  257. curHand = (_currentStep + 1) % 2;
  258. add(GamePlayerEvent(curHand));
  259. add(GameLoadEvent(_currentStep + 1));
  260. logger.info('history $_currentStep');
  261. }
  262. /// 切换驱动
  263. void switchDriver(int team, DriverType driverType) {
  264. hands[team].driverType = driverType;
  265. if (team == curHand && driverType == DriverType.robot) {
  266. //add(GameLockEvent(true));
  267. next();
  268. } else if (driverType == DriverType.user) {
  269. //add(GameLockEvent(false));
  270. }
  271. }
  272. /// 调用对应的玩家开始下一步
  273. Future<void> next() async {
  274. final move = await player.move();
  275. if (move == null) return;
  276. addMove(move);
  277. final canNext = checkResult(curHand == 0 ? 1 : 0, _currentStep - 1);
  278. logger.info('canNext $canNext');
  279. if (canNext) {
  280. switchPlayer();
  281. }
  282. }
  283. /// 从用户落着 TODO 检查出发点是否有子,检查落点是否对方子
  284. void addStep(ChessPos from, ChessPos next) async {
  285. player.completeMove('${from.toCode()}${next.toCode()}');
  286. }
  287. void addMove(String move) {
  288. logger.info('addmove $move');
  289. if (PlayerDriver.isAction(move)) {
  290. if (move == PlayerDriver.rstGiveUp) {
  291. setResult(
  292. curHand == 0 ? ChessManual.resultFstLoose : ChessManual.resultFstWin,
  293. '${player.title}认输',
  294. );
  295. }
  296. if (move == PlayerDriver.rstDraw) {
  297. setResult(ChessManual.resultFstDraw);
  298. }
  299. if (move == PlayerDriver.rstRetract) {
  300. // todo 悔棋
  301. }
  302. if (move.contains(PlayerDriver.rstRqstDraw)) {
  303. move = move.replaceAll(PlayerDriver.rstRqstDraw, '').trim();
  304. if (move.isEmpty) {
  305. return;
  306. }
  307. } else {
  308. return;
  309. }
  310. }
  311. if (!ChessManual.isPosMove(move)) {
  312. logger.info('着法错误 $move');
  313. return;
  314. }
  315. // 如果当前不是最后一步,移除后面着法
  316. if (!manual.isLast) {
  317. add(GameLoadEvent(-2));
  318. add(GameStepEvent('clear'));
  319. manual.addMove(move, addStep: _currentStep);
  320. } else {
  321. add(GameLoadEvent(-2));
  322. manual.addMove(move);
  323. }
  324. _currentStep = manual.currentStep;
  325. final curMove = manual.currentMove!;
  326. if (curMove.isCheckMate) {
  327. unEatCount++;
  328. Sound.play(Sound.move);
  329. } else if (curMove.isEat) {
  330. unEatCount = 0;
  331. Sound.play(Sound.capture);
  332. } else {
  333. unEatCount++;
  334. Sound.play(Sound.move);
  335. }
  336. add(GameStepEvent(curMove.toChineseString()));
  337. }
  338. void setResult(String result, [String description = '']) {
  339. if (!ChessManual.results.contains(result)) {
  340. logger.info('结果不合法 $result');
  341. return;
  342. }
  343. logger.info('本局结果:$result');
  344. add(GameResultEvent('$result $description'));
  345. if (result == ChessManual.resultFstDraw) {
  346. Sound.play(Sound.draw);
  347. } else if (result == ChessManual.resultFstWin) {
  348. Sound.play(Sound.win);
  349. } else if (result == ChessManual.resultFstLoose) {
  350. Sound.play(Sound.loose);
  351. }
  352. manual.result = result;
  353. }
  354. /// 棋局结果判断
  355. bool checkResult(int hand, int curMove) {
  356. logger.info('checkResult');
  357. int repeatRound = manual.repeatRound();
  358. if (repeatRound > 2) {
  359. // TODO 提醒
  360. }
  361. // 判断和棋
  362. if (unEatCount >= 120) {
  363. setResult(ChessManual.resultFstDraw, '60回合无吃子判和');
  364. return false;
  365. }
  366. //isCheckMate = rule.isCheck(hand);
  367. final moveStep = manual.currentMove!;
  368. logger.info('是否将军 ${moveStep.isCheckMate}');
  369. // 判断输赢,包括能否应将,长将
  370. if (moveStep.isCheckMate) {
  371. //manual.moves[curMove].isCheckMate = isCheckMate;
  372. if (rule.canParryKill(hand)) {
  373. // 长将
  374. if (repeatRound > 3) {
  375. setResult(
  376. hand == 0 ? ChessManual.resultFstLoose : ChessManual.resultFstWin,
  377. '不变招长将作负',
  378. );
  379. return false;
  380. }
  381. Sound.play(Sound.check);
  382. add(GameResultEvent('checkMate'));
  383. } else {
  384. setResult(
  385. hand == 0 ? ChessManual.resultFstLoose : ChessManual.resultFstWin,
  386. '绝杀',
  387. );
  388. return false;
  389. }
  390. } else {
  391. if (rule.isTrapped(hand)) {
  392. setResult(
  393. hand == 0 ? ChessManual.resultFstLoose : ChessManual.resultFstWin,
  394. '困毙',
  395. );
  396. return false;
  397. } else if (moveStep.isEat) {
  398. add(GameResultEvent('eat'));
  399. }
  400. }
  401. // TODO 判断长捉,一捉一将,一将一杀
  402. if (repeatRound > 3) {
  403. setResult(ChessManual.resultFstDraw, '不变招判和');
  404. return false;
  405. }
  406. return true;
  407. }
  408. List<String> getSteps() {
  409. return manual.moves.map<String>((cs) => cs.toChineseString()).toList();
  410. }
  411. void dispose() {
  412. if (engine != null) {
  413. engine?.stop();
  414. engine?.quit();
  415. engine = null;
  416. }
  417. }
  418. void switchPlayer() {
  419. curHand++;
  420. if (curHand >= hands.length) {
  421. curHand = 0;
  422. }
  423. add(GamePlayerEvent(curHand));
  424. logger.info('切换选手:${player.title} ${player.team} ${player.driver}');
  425. logger.info(player.title);
  426. next();
  427. add(GameEngineEvent('clear'));
  428. }
  429. Future<bool> startEngine() {
  430. if (engine != null) {
  431. return Future.value(true);
  432. }
  433. Completer<bool> engineFuture = Completer<bool>();
  434. engine = Engine(EngineType.pikafish);
  435. engineOK = false;
  436. engine?.init().then((Process? v) {
  437. engineOK = true;
  438. engine?.addListener(parseMessage);
  439. engineFuture.complete(true);
  440. });
  441. return engineFuture.future;
  442. }
  443. void requestHelp() {
  444. if (engine == null) {
  445. startEngine().then((v) {
  446. if (v) {
  447. requestHelp();
  448. } else {
  449. logger.info('engine is not support');
  450. }
  451. });
  452. } else {
  453. if (engineOK) {
  454. isStop = true;
  455. engine?.stop();
  456. engine?.position(fenStr);
  457. engine?.go(depth: 10);
  458. } else {
  459. logger.info('engine is not ok');
  460. }
  461. }
  462. }
  463. String get fenStr => '${manual.currentFen.fen} ${curHand > 0 ? 'b' : 'w'}'
  464. ' - - $unEatCount ${manual.moveCount ~/ 2}';
  465. Player get player => hands[curHand];
  466. Player getPlayer(int hand) => hands[hand];
  467. }