game.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. import 'dart:async';
  2. import 'dart:math';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:get/get.dart';
  5. import 'package:fixnum/fixnum.dart';
  6. import 'package:grpc/grpc.dart';
  7. import 'package:trackoffical_app/model/game_map.dart';
  8. import 'package:trackoffical_app/model/settlement.dart';
  9. import 'package:trackoffical_app/pb.dart' as pb;
  10. import 'package:trackoffical_app/service/app_map.dart';
  11. import 'package:trackoffical_app/service/game/game_model.dart';
  12. import 'package:trackoffical_app/service/game/plug.dart';
  13. import 'package:trackoffical_app/service/game/plug_location.dart';
  14. import 'package:trackoffical_app/service/game/plug_sport_wear.dart';
  15. import 'package:trackoffical_app/service/game/rule_in_order.dart';
  16. import 'package:trackoffical_app/service/game/show_position_controller.dart';
  17. import 'package:trackoffical_app/utils.dart';
  18. import 'package:trackoffical_app/view/ingame/settlement_view.dart';
  19. import 'package:vibration/vibration.dart';
  20. import 'package:wakelock/wakelock.dart';
  21. import '../../logger.dart';
  22. import '../api.dart';
  23. import '../../model.dart';
  24. import '../app.dart';
  25. import '../database.dart';
  26. import 'map_status.dart';
  27. import 'plug_orientation.dart';
  28. import 'rule.dart';
  29. import '../image.dart';
  30. enum GameStatus {
  31. idle,
  32. // 准备进入游戏
  33. preparing,
  34. loading,
  35. playing,
  36. settlement,
  37. }
  38. class GameService extends GetxService {
  39. static GameService get to => Get.find();
  40. final DatabaseService _database = Get.find();
  41. final GameModel _model = Get.find();
  42. final App _app = Get.find();
  43. final errorMsg = ''.obs;
  44. final name = "".obs;
  45. var activityId = 0;
  46. var mapRouteId = Int64(0);
  47. var _status = GameStatus.idle;
  48. Rule rule = RuleMock();
  49. GameStatus get status => _status;
  50. set status(GameStatus v) {
  51. info('游戏状态:[$v]');
  52. _status = v;
  53. }
  54. pb.GameSettlement _lastGameSettlement = pb.GameDetailReply();
  55. pb.GameSettlement get lastGameSettlement => _lastGameSettlement;
  56. MPosition? get myPosition => _model.myPosition.value;
  57. Offset? get positionOnMap => _model.myPositionOnMap.value;
  58. double get myPositionHistoryLenKm => _model.myPositionHistoryLen.value.km;
  59. final _plugs = <Plug>[];
  60. GameState get gameState => _model.gameSrcState.value;
  61. var isShowCompass = true.obs;
  62. static const _progressMap = 0.6;
  63. static const _progressApi = 0.2;
  64. static const _progressFinal = 0.2;
  65. final _loadProgress = 0.0.obs;
  66. double get loadProgress => _loadProgress.value;
  67. List<MControlPoint> get controlPointWantSequence =>
  68. _model.controlPointWantSequence;
  69. bool get isNfcScanUseDialog {
  70. final platform = _app.platformInfo;
  71. if (platform is PlatformInfoIOS) {
  72. if (platform.deviceVersion >= 8) {
  73. return true;
  74. }
  75. }
  76. return false;
  77. }
  78. // 创建时间
  79. DateTime get createTime => gameState.createTime;
  80. // 关门时间
  81. Duration get maxPassDuration => gameState.pbGameData.maxDuration.toDuration();
  82. // 强制结束时间
  83. Duration get maxForcedEndDuration =>
  84. gameState.pbGameData.maxForcedEndDuration.toDuration();
  85. DateTime get now => _app.now;
  86. // 打开始点的时间
  87. DateTime? get startAt => _model.startAt;
  88. DateTime? get endAt => _model.endAt;
  89. List<pb.ControlPoint> get _controlPointWantSequence =>
  90. gameState.pbGameData.controlPointSortedList;
  91. List<pb.ControlPointSimple> get _controlPointAll =>
  92. gameState.pbGameData.controlPointAll;
  93. var _lastCheckedPoint = Int64(0);
  94. var _lastCheckedPointTime = DateTime(0);
  95. List<MControlPoint> get checkedPointsHistory => _model.checkedPointsHistory;
  96. final beginDuration = 0.seconds.obs;
  97. final mapStatus = MapStatus();
  98. Timer? _beginDurationTicker;
  99. Timer? _checkStopTicker;
  100. int get checkedCount => _model.checkedCount;
  101. // 所有计分点数量
  102. int get controlPointAllNum => _model.validCPAllNum;
  103. // 打卡进度
  104. double get checkProgress => _model.checkProgress;
  105. bool get isOutBoundary {
  106. final p = _model.myPositionOnMap.value;
  107. if (p != null) {
  108. final mapWidth = mapStatus.gameMapData.width;
  109. final mapHeight = mapStatus.gameMapData.height;
  110. if (p.dx <= 0 || p.dx >= mapWidth || p.dy <= 0 || p.dy >= mapHeight) {
  111. return true;
  112. }
  113. }
  114. return false;
  115. }
  116. AnimationController? showLocationController;
  117. showLocation() {
  118. final p = positionOnMap;
  119. if (p != null) {
  120. final dst = _model.mapRotateCenter;
  121. if (_model.isEnableUserLocation && !_model.isAlwaysShowMyLocation) {
  122. Get.find<ShowPositionController>().show();
  123. }
  124. if (dst != null) {
  125. mapStatus.movePicPointTo(p, dst);
  126. }
  127. }
  128. }
  129. _setStartAt(DateTime? time) {
  130. if (time != null) {
  131. gameState.pbGameSave.startAt = time.toPb();
  132. } else {
  133. gameState.pbGameSave.clearStartAt();
  134. }
  135. _model.gameSrcState.refresh();
  136. }
  137. _setEndAt(DateTime? time) {
  138. if (time != null) {
  139. gameState.pbGameSave.stopAt = time.toPb();
  140. } else {
  141. gameState.pbGameSave.clearStopAt();
  142. }
  143. _model.gameSrcState.refresh();
  144. }
  145. _recordLastPoint(Int64 id) {
  146. _lastCheckedPoint = id;
  147. _lastCheckedPointTime = now;
  148. }
  149. bool _isCheckTooFast(Int64 id) {
  150. return _lastCheckedPoint == id &&
  151. now.difference(_lastCheckedPointTime) <= 10.seconds;
  152. }
  153. bool isPointStart(pb.GameSaveControlPoint p) {
  154. final seq = _controlPointWantSequence;
  155. if (seq.isNotEmpty) {
  156. return p.controlPointId == seq.first.id;
  157. }
  158. return false;
  159. }
  160. bool isPointFinish(pb.GameSaveControlPoint p) {
  161. final seq = _controlPointWantSequence;
  162. if (seq.isNotEmpty) {
  163. return p.controlPointId == seq.last.id;
  164. }
  165. return false;
  166. }
  167. void setBeginMatrix() {
  168. if (mapStatus.isSetBeginMatrix) {
  169. return;
  170. }
  171. final next = _model.nextPlanPoint;
  172. final f = _model.mapRotateCenter;
  173. if (next != null && f != null) {
  174. mapStatus.movePicPointTo(next.onMap, f);
  175. mapStatus.isSetBeginMatrix = true;
  176. }
  177. }
  178. pb.ControlPointSimple gameSaveControlPointFindInProject(
  179. pb.GameSaveControlPoint p) {
  180. final all = gameState.pbGameData.controlPointAll;
  181. for (final one in all) {
  182. if (one.id == p.controlPointId) {
  183. return one;
  184. }
  185. }
  186. return pb.ControlPointSimple();
  187. }
  188. Future<List<MControlPoint>> _getControlPointWantSequence() async {
  189. final out = <MControlPoint>[];
  190. final checkedIndex = checkedCount;
  191. for (var i = 0; i < _controlPointWantSequence.length; i++) {
  192. final value = _controlPointWantSequence[i];
  193. final one = value.toModel();
  194. one.sn = '$i';
  195. if (i == 0) {
  196. one.isStart = true;
  197. }
  198. if (i == _controlPointWantSequence.length - 1) {
  199. one.isFinish = true;
  200. }
  201. one.isSuccess = i < checkedIndex;
  202. one.isNext = i == checkedIndex;
  203. one.onMap =
  204. await mapStatus.gameMapData.worldToPixel(value.mapPosition.toModel());
  205. final extraInfo = one.extraInfo;
  206. if(extraInfo is CPExtraInfoChoiceQuestion){
  207. extraInfo.beanCount = gameState.pbGameData.answerSysPoint;
  208. await extraInfo.image?.loadMemory();
  209. }
  210. out.add(one);
  211. }
  212. return out;
  213. }
  214. MControlPoint? getNextWantPoint(int offset) =>
  215. _model.getNextWantPoint(offset);
  216. MControlPoint? getLastCheckedPoint() {
  217. if (checkedPointsHistory.isNotEmpty) {
  218. return checkedPointsHistory.last;
  219. } else {
  220. return null;
  221. }
  222. }
  223. void _checkGameStop({bool willToSettlementView = false}) {
  224. if ([
  225. GameStatus.preparing,
  226. GameStatus.playing,
  227. ].contains(status)) {
  228. if (now.difference(createTime) > maxForcedEndDuration) {
  229. gameGiveUp(willToSettlementView: willToSettlementView);
  230. }
  231. }
  232. }
  233. Future<void> _saveToDatabase() async {
  234. await DatabaseService.to.saveGameState(gameState);
  235. }
  236. Future<void> _saveToServer() async {
  237. while (!isClosed) {
  238. try {
  239. final save = gameState.pbGameSave;
  240. await ApiService.to
  241. .gameSaveUpload(pb.GameSaveUploadRequest()..gameSave= save);
  242. return;
  243. } on GrpcError catch (e) {
  244. warn('上传失败:', e);
  245. if (e.code != StatusCode.unavailable) {
  246. return;
  247. }
  248. } catch (e) {
  249. warn('上传失败:', e);
  250. return;
  251. }
  252. await Future.delayed(500.milliseconds);
  253. }
  254. }
  255. void _checkHistoryAdd(MControlPoint cp) {
  256. checkedPointsHistory.add(cp);
  257. gameState.pbGameSave.checkedSortedList.add(cp.toPbSave());
  258. }
  259. void _updateCheckHistory(){
  260. gameState.pbGameSave.checkedSortedList.clear();
  261. gameState.pbGameSave.checkedSortedList.addAll(checkedPointsHistory.map((e) => e.toPbSave()).toList());
  262. }
  263. void save() {
  264. _updateCheckHistory();
  265. _saveToDatabase().then((value) => info('本地存档完成'));
  266. _saveToServer().then((value) => info('上传存档完成'));
  267. }
  268. Future<void> gameGiveUp({bool willToSettlementView = false}) async {
  269. // if(status == GameStatus.idle || status == GameStatus.settlement){
  270. // return;
  271. // }
  272. _gameStopSetValues();
  273. _updateCheckHistory();
  274. _saveToDatabase().then((value) => info('本地存档完成'));
  275. _settlement(isGiveUp: true, willToSettlementView: willToSettlementView);
  276. }
  277. void _gameStopSetValues({DateTime? now}) {
  278. info('游戏结束');
  279. if (now != null) {
  280. _setEndAt(now);
  281. } else {
  282. _setEndAt(this.now);
  283. }
  284. }
  285. void _updateNewCPState(MControlPoint point, {DateTime? now}) {
  286. now = now ?? this.now;
  287. point.checkAfterStart = now.difference(startAt ?? now);
  288. point.checkAfterPrev = point.checkAfterStart;
  289. point.checkDistanceAfterStart = _model.myPositionHistoryLen.value;
  290. point.checkDistanceAfterPrev = point.checkDistanceAfterStart;
  291. final last = getLastCheckedPoint();
  292. if (last != null) {
  293. point.checkAfterPrev = point.checkAfterStart - last.checkAfterStart;
  294. point.checkDistanceAfterPrev = point.checkDistanceAfterStart - last.checkDistanceAfterStart;
  295. }
  296. }
  297. checkPointNFC(
  298. String identifier,
  299. void Function(MControlPoint cp) onChecked,
  300. Future<bool> Function () onConfirmFinish,
  301. void Function(pb.ControlPointSimple point) onProjectPoint,
  302. VoidCallback onNoPoint,
  303. ) {
  304. final checkedPoint = _findControlPointInRouteByNfcId(identifier);
  305. if (checkedPoint != null) {
  306. checkPoint(checkedPoint, onChecked, onConfirmFinish);
  307. } else {
  308. final point = _findControlPointInProjectByNfcId(identifier);
  309. if (point != null) {
  310. if (_isCheckTooFast(point.id)) {
  311. return;
  312. }
  313. final result = point.toModel();
  314. _updateNewCPState(result);
  315. result.isSuccess = false;
  316. _checkHistoryAdd(result);
  317. save();
  318. _recordLastPoint(point.id);
  319. onProjectPoint(point);
  320. } else {
  321. onNoPoint();
  322. }
  323. }
  324. }
  325. checkPointGps(
  326. void Function(MControlPoint cp) onChecked,
  327. Future<bool> Function () onConfirmFinish,
  328. ) {
  329. if(_model.isInPlanControlPointArea){
  330. pb.ControlPoint? checkedPoint;
  331. final point = _model.nextPlanPoint;
  332. if (point != null) {
  333. checkedPoint = _findControlPointInRouteByM(point);
  334. }
  335. checkPoint(checkedPoint!, onChecked, onConfirmFinish);
  336. }else if (_model.isInWantControlPointArea) {
  337. pb.ControlPoint? checkedPoint;
  338. final point = _model.getNextWantPoint(0);
  339. if (point != null) {
  340. checkedPoint = _findControlPointInRouteByM(point);
  341. }
  342. checkPoint(checkedPoint!, onChecked, onConfirmFinish);
  343. }
  344. }
  345. Future<void> checkPoint(
  346. pb.ControlPoint checkedPoint,
  347. void Function (MControlPoint cp) onChecked,
  348. /// 打了结束点,询问是否结束
  349. Future<bool> Function () onConfirmFinish,
  350. ) async{
  351. final cp = checkedPoint.toModel();
  352. if (rule.checkNeedReturn(cp)){
  353. return;
  354. }
  355. final now = this.now;
  356. final result = rule.checkPoint(checkedPoint.toModel());
  357. var isFinish = result.isSuccess && result.isFinish;
  358. if(!result.isSuccess && result.isFinish){
  359. isFinish = await onConfirmFinish();
  360. if(!isFinish){
  361. return;
  362. }
  363. }
  364. _updateNewCPState(result, now: now);
  365. if(result.isStart && result.isSuccess){
  366. _setStartAt(now);
  367. }
  368. if(isFinish){
  369. _gameStopSetValues(now: now);
  370. }
  371. _checkHistoryAdd(result);
  372. if(startAt!=null){
  373. gameState.pbGameSave.duration = now.difference(startAt!).toPb();
  374. if(endAt != null){
  375. gameState.pbGameSave.duration = endAt!.difference(startAt!).toPb();
  376. }
  377. }
  378. _model.gameSrcState.refresh();
  379. if(isFinish){
  380. _settlement();
  381. }else{
  382. save();
  383. }
  384. rule.recordLastPoint(cp);
  385. onChecked(result);
  386. }
  387. void showNextPoint() {
  388. final next = _model.nextPlanPoint;
  389. final focalPoint = _model.mapRotateCenter;
  390. if (next != null && focalPoint != null) {
  391. mapStatus.movePicPointTo(next.onMap, focalPoint);
  392. }
  393. }
  394. Future<void> gameGiveUpAndToFinishView({GameState? state}) async {
  395. if (state != null) {
  396. _model.gameSrcState.value = state;
  397. }
  398. gameGiveUp();
  399. SettlementView.show();
  400. // Get.offAll(() => const GameFinishView(),
  401. // binding: GameFinishView.bindings());
  402. }
  403. Future<void> _gameReset() async {
  404. errorMsg.value = '';
  405. _model.clear();
  406. await _plugsClear();
  407. }
  408. Future<void> gameStart() async {
  409. info('项目Id[$activityId]准备开始');
  410. await _gameReset();
  411. final data = await ApiService.to.gameStart(activityId, mapRouteId);
  412. _model.gameSrcState.value = data.toGameState();
  413. status = GameStatus.preparing;
  414. _saveToDatabase();
  415. info('活动Id[$activityId],游戏Id[${gameState.pbGameData.gameId}]已开始');
  416. }
  417. Future<void> gameLoad() async {
  418. info('载入项目[$activityId], Id[${gameState.pbGameData.gameId}]');
  419. await _plugsClear();
  420. _loadProgress.value = 0;
  421. status = GameStatus.loading;
  422. _app.userProfile.cleanGameSettingsLock();
  423. _app.userProfile.gameSettingsLoadLock(gameState.pbGameData.ruleList);
  424. /// 游戏规则
  425. // rule = RuleInOrder();
  426. _model.compassRealNorthOffset = gameState.pbGameData.declination * pi / 180;
  427. _loadProgress.value = _progressApi;
  428. final gameMap = gameState.pbGameData.mapZip.toGameMap();
  429. mapStatus.gameMapData = gameMap;
  430. await gameMap.loadMemory(onReceiveProgress: (c, a) {
  431. if (a > 0) {
  432. var p = c.toDouble() / a;
  433. p = p * _progressMap + _progressApi;
  434. _loadProgress.value = p;
  435. }
  436. });
  437. mapStatus.mapImageData.value = gameMap.pic!;
  438. // --- 确定地图图片首次缩放比例 ---
  439. final screenSize = _app.screenSize;
  440. final fitted = applyBoxFit(
  441. BoxFit.contain,
  442. Size(gameMap.width, gameMap.height),
  443. Size(screenSize.width, screenSize.height));
  444. mapStatus.picFirstScale = fitted.destination.width / fitted.source.width;
  445. // --------------
  446. _model.mapRotateCenter =
  447. Offset(screenSize.width / 2, screenSize.height / 2);
  448. await mapStatus.resetMatrix();
  449. _model.controlPointWantSequence.value =
  450. await _getControlPointWantSequence();
  451. _gameLoadCheckedCP();
  452. final plugLocation = PlugLocation(
  453. gameMap: gameMap,
  454. lastInfo: _model.gameSrcState.value.pbGameSave.gameGpsInfos);
  455. _plugs.add(plugLocation);
  456. _plugs.add(PlugSportWear(
  457. lastHr: _model.gameSrcState.value.pbGameSave.gameHrInfos));
  458. _plugs.add(PlugOrientation(mapStatus: mapStatus));
  459. await _plugsAllInit();
  460. _loadProgress.value = 1;
  461. status = GameStatus.playing;
  462. Wakelock.enable();
  463. info('载入完成');
  464. }
  465. void _gameLoadCheckedCP() {
  466. final controlPointWantIdValueMap = <Int64, MControlPoint>{};
  467. final controlPointAllIdValueMap = <Int64, pb.ControlPointSimple>{};
  468. for (var one in _model.controlPointWantSequence) {
  469. controlPointWantIdValueMap[one.intId] = one;
  470. }
  471. for (var one in gameState.pbGameData.controlPointAll) {
  472. controlPointAllIdValueMap[one.id] = one;
  473. }
  474. for (var i = 0; i < gameState.pbGameSave.checkedSortedList.length; i++) {
  475. final one = gameState.pbGameSave.checkedSortedList[i];
  476. final his = one.toModel();
  477. final hisInfo1 = controlPointWantIdValueMap[his.intId];
  478. final hisInfo2 = controlPointAllIdValueMap[his.intId];
  479. if (hisInfo2 != null) {
  480. his.updateBySimple(hisInfo2);
  481. }
  482. if (hisInfo1 != null) {
  483. his.updateBy(hisInfo1);
  484. his.type = hisInfo1.type;
  485. }
  486. checkedPointsHistory.add(his);
  487. }
  488. for (var i = 0; i < checkedPointsHistory.length; i++) {
  489. final his = checkedPointsHistory[i];
  490. if (i > 0) {
  491. final last = checkedPointsHistory[i - 1];
  492. his.checkAfterPrev = his.checkAfterStart - last.checkAfterStart;
  493. his.checkDistanceAfterPrev = his.checkDistanceAfterStart - last.checkDistanceAfterStart;
  494. } else {
  495. his.checkAfterPrev = his.checkAfterStart;
  496. his.checkDistanceAfterPrev = his.checkDistanceAfterStart;
  497. }
  498. }
  499. }
  500. Future<void> _plugsAllInit() async {
  501. for (var one in _plugs) {
  502. await one.init();
  503. }
  504. }
  505. Future<void> _plugsClear() async {
  506. for (var one in _plugs) {
  507. one.close();
  508. }
  509. for (var one in _plugs) {
  510. await one.join();
  511. }
  512. _plugs.clear();
  513. }
  514. Settlement getSettlement() {
  515. return Settlement(
  516. data: gameState.pbGameData,
  517. save: gameState.pbGameSave,
  518. durationAfterStartCheck: beginDuration.value);
  519. }
  520. pb.ControlPoint? _findControlPointInRouteByNfcId(String identifier) {
  521. pb.ControlPoint? found;
  522. for (var one in _controlPointWantSequence) {
  523. for (var nfcId in one.nfcIdList) {
  524. if (nfcId.toUpperCase() == identifier.toUpperCase()) {
  525. found = one;
  526. break;
  527. }
  528. }
  529. }
  530. return found;
  531. }
  532. pb.ControlPoint? _findControlPointInRouteByM(MControlPoint point) {
  533. pb.ControlPoint? found;
  534. for (var one in _controlPointWantSequence) {
  535. if (one.id == point.intId) {
  536. found = one;
  537. break;
  538. }
  539. }
  540. return found;
  541. }
  542. pb.ControlPointSimple? _findControlPointInProjectByNfcId(String identifier) {
  543. for (var one in _controlPointAll) {
  544. for (var nfcId in one.nfcIdList) {
  545. if (nfcId.toUpperCase() == identifier.toUpperCase()) {
  546. return one;
  547. }
  548. }
  549. }
  550. return null;
  551. }
  552. Future<void> _settlementDeal(bool isGiveUp) async {
  553. while (!isClosed) {
  554. try {
  555. final save = gameState.pbGameSave;
  556. await ApiService.to
  557. .gameSaveUpload(pb.GameSaveUploadRequest()..gameSave= save);
  558. info('进度已上传');
  559. break;
  560. } on GrpcError catch (e) {
  561. if (e.code == StatusCode.unavailable) {
  562. errorMsg.value = '无法连接至服务器,正在重试';
  563. } else {
  564. break;
  565. }
  566. error(e);
  567. } catch (e) {
  568. error(e);
  569. break;
  570. }
  571. }
  572. await _plugsClear();
  573. while (!isClosed) {
  574. try {
  575. info('正在结算');
  576. _lastGameSettlement = await ApiService.to
  577. .gameFinish(gameState.pbGameData.gameId, isGiveUp);
  578. info('结算完成');
  579. break;
  580. } on GrpcError catch (e) {
  581. if (e.code == StatusCode.unavailable) {
  582. errorMsg.value = '网络错误,正在重试';
  583. } else {
  584. break;
  585. }
  586. error(e);
  587. Future.delayed(500.milliseconds);
  588. } catch (e) {
  589. error(e);
  590. break;
  591. }
  592. }
  593. await _saveToDatabase();
  594. status = GameStatus.idle;
  595. }
  596. _settlement({bool isGiveUp = false, bool willToSettlementView = false}) {
  597. _updateCheckHistory();
  598. status = GameStatus.settlement;
  599. errorMsg.value = '';
  600. _settlementDeal(isGiveUp).then((value) => info('结算完成'));
  601. if (willToSettlementView) {
  602. SettlementView.show();
  603. }
  604. }
  605. @override
  606. void onReady() {
  607. _beginDurationTicker = Timer.periodic(100.milliseconds, (timer) {
  608. final startAt = this.startAt;
  609. if (startAt != null) {
  610. final end = endAt ?? now;
  611. beginDuration.value = end.difference(startAt);
  612. } else {
  613. beginDuration.value = 0.seconds;
  614. }
  615. });
  616. }
  617. @override
  618. void onClose() {
  619. _checkStopTicker?.cancel();
  620. _beginDurationTicker?.cancel();
  621. }
  622. Future<bool> loadOnlineUnFinishGame() async {
  623. try {
  624. final online = await ApiService.to.getInGameData();
  625. info('存在线上未完成游戏');
  626. status = GameStatus.preparing;
  627. _model.gameSrcState.value = GameState()
  628. ..pbGameData = online.data
  629. ..pbGameSave = online.save;
  630. _checkGameStop();
  631. return true;
  632. } on GrpcError catch (e) {
  633. if (e.code != StatusCode.notFound) {
  634. warn(e);
  635. }
  636. return false;
  637. } catch (e) {
  638. warn(e);
  639. return false;
  640. }
  641. }
  642. void workAutoSave() async {
  643. while (!isClosed) {
  644. await Future.delayed(500.milliseconds);
  645. if (_model.gameSrcState.value.pbGameData.gameId > 0 &&
  646. _model.isStarted &&
  647. !_model.isFinish) {
  648. await _saveToDatabase();
  649. }
  650. }
  651. }
  652. void workTrajectory() async {
  653. while (!isClosed) {
  654. await Future.delayed(1000.milliseconds);
  655. final duration = _app.userProfile.inGameTrajectorySeconds.val.seconds;
  656. var out = <Offset>[];
  657. for (var i = _model.myPositionHistory.length - 1; i >= 0; i--) {
  658. var one = _model.myPositionHistory[i];
  659. if (now.difference(one.timestamp) > duration) {
  660. break;
  661. }
  662. out.add(await mapStatus.gameMapData.worldToPixel(one));
  663. }
  664. _model.trajectoryPoints.clear();
  665. _model.trajectoryPoints.addAll(out);
  666. }
  667. }
  668. void workPace() async {
  669. while (!isClosed) {
  670. await Future.delayed(1000.milliseconds);
  671. final startDuration = _model.startedDuration;
  672. if (startDuration.inMilliseconds == 0) {
  673. continue;
  674. }
  675. _model.paceSecondKm.value =
  676. pacePerKm(myPositionHistoryLenKm.km, startDuration);
  677. if (checkedPointsHistory.isEmpty) {
  678. _model.paceSecondKmFromLastCP.value = _model.paceSecondKm.value;
  679. } else {
  680. final cp = checkedPointsHistory.last;
  681. _model.paceSecondKmFromLastCP.value = pacePerKm(
  682. _model.myPositionHistoryLenFromLastCP,
  683. startDuration - cp.checkAfterStart);
  684. }
  685. }
  686. }
  687. static Future<GameService> init() async {
  688. final gs = GameService();
  689. gs.mapStatus.canVibrate = await Vibration.hasVibrator() ?? false;
  690. final save = await gs._database.getExistGameData();
  691. if (save != null) {
  692. info('存在本地未完成游戏');
  693. gs.status = GameStatus.preparing;
  694. gs._model.gameSrcState.value = save.toState();
  695. gs._checkGameStop();
  696. } else {
  697. gs.loadOnlineUnFinishGame();
  698. }
  699. gs._checkStopTicker = Timer.periodic(100.milliseconds, (timer) {
  700. gs._checkGameStop(willToSettlementView: true);
  701. });
  702. gs.workAutoSave();
  703. gs.workTrajectory();
  704. gs.workPace();
  705. return gs;
  706. }
  707. }