game_controller.dart 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. import 'package:get/get.dart';
  4. import 'package:gobang/models/chess.dart';
  5. import 'package:gobang/models/checkerboard.dart';
  6. import 'package:gobang/models/chess_shape.dart';
  7. import 'package:gobang/models/circle_shape.dart';
  8. import 'package:gobang/models/game_state.dart';
  9. import 'package:gobang/models/position.dart';
  10. import 'package:gobang/models/rect_shape.dart';
  11. import 'package:gobang/models/user_context.dart';
  12. import 'package:gobang/services/ai_service.dart';
  13. import 'package:gobang/services/chess_flyweight_factory.dart';
  14. import 'package:gobang/themes/black_theme_factory.dart';
  15. import 'package:gobang/themes/blue_theme_factory.dart';
  16. import 'package:gobang/themes/theme_factory.dart';
  17. import 'package:gobang/utils/constants.dart';
  18. import 'package:gobang/utils/tips_dialog.dart';
  19. /// 对局控制器:棋盘交互、AI、主题与阶段状态
  20. class GameController extends GetxController {
  21. final UserContext _userContext = UserContext();
  22. final Checkerboard board = Checkerboard.getInstance();
  23. final AiService ai = AiService.getInstance();
  24. final themeFactory = Rx<ThemeFactory>(BlueThemeFactory());
  25. final useCircle = true.obs;
  26. final gameOver = false.obs;
  27. final paintVersion = 0.obs;
  28. final statusText = '热身阶段,不能悔棋,不能投降'.obs;
  29. static const lightOn = Icon(Icons.lightbulb, color: Colors.amberAccent);
  30. static const lightOff = Icon(Icons.lightbulb_outline_rounded);
  31. static const circleIcon = Icon(Icons.circle_outlined);
  32. static const rectIcon = Icon(Icons.crop_square);
  33. Icon get currentLight =>
  34. themeFactory.value is BlackThemeFactory ? lightOff : lightOn;
  35. Icon get currentShape => useCircle.value ? circleIcon : rectIcon;
  36. Color get themeColor => themeFactory.value.getTheme().getThemeColor();
  37. @override
  38. void onInit() {
  39. super.onInit();
  40. ai.init();
  41. _refreshStatus();
  42. }
  43. void toggleTheme() {
  44. themeFactory.value = themeFactory.value is BlackThemeFactory
  45. ? BlueThemeFactory()
  46. : BlackThemeFactory();
  47. }
  48. void toggleShape() => useCircle.toggle();
  49. ChessShape _shapeFor(bool circle) =>
  50. circle ? CircleShape() : RectShape();
  51. void _refreshStatus() {
  52. final s = _userContext.state;
  53. if (s is StartState) {
  54. statusText.value = '热身阶段,不能悔棋,不能投降';
  55. } else if (s is MidState) {
  56. statusText.value = '入神阶段,可以悔棋且剩余${3 - s.reg}次,可以投降';
  57. } else if (s is EndState) {
  58. statusText.value = '白热化阶段,悔棋次数已用完,但可以投降';
  59. }
  60. }
  61. void onBoardTap(Offset local, double boardWidth) {
  62. if (gameOver.value) return;
  63. final cell = boardWidth / (kBoardSize - 1);
  64. final snap = _snapToGrid(local, cell);
  65. if (snap == null) return;
  66. final (gridX, gridY, px, py) = snap;
  67. if (!ai.isLegal(gridX, gridY)) return;
  68. _userContext.play();
  69. final chess = ChessFlyweightFactory.getInstance().getChess('white');
  70. board.add(Position(
  71. px,
  72. py,
  73. chess,
  74. gridX: gridX,
  75. gridY: gridY,
  76. chessShape: _shapeFor(useCircle.value),
  77. ));
  78. ai.addChessman(gridX, gridY, 1);
  79. paintVersion.value++;
  80. _refreshStatus();
  81. if (ai.isWin(gridX, gridY, 1)) {
  82. gameOver.value = true;
  83. TipsDialog.show(Get.context!, '恭喜', '您打败了决策树算法');
  84. return;
  85. }
  86. _playAi(cell);
  87. }
  88. (int, int, double, double)? _snapToGrid(Offset local, double cell) {
  89. final gx = (local.dx / cell).round().clamp(0, kBoardSize - 1);
  90. final gy = (local.dy / cell).round().clamp(0, kBoardSize - 1);
  91. final px = gx * cell;
  92. final py = gy * cell;
  93. final dist = sqrt(pow(local.dx - px, 2) + pow(local.dy - py, 2));
  94. if (dist > cell / 2 - 2) return null;
  95. return (gx, gy, px, py);
  96. }
  97. void _playAi(double cell) {
  98. final raw = ai.searchPosition();
  99. if (raw.dx < 0 || raw.dy < 0) return;
  100. final gridX = raw.dx.toInt();
  101. final gridY = raw.dy.toInt();
  102. board.add(Position(
  103. gridX * cell,
  104. gridY * cell,
  105. raw.chess,
  106. gridX: gridX,
  107. gridY: gridY,
  108. chessShape: CircleShape(),
  109. ));
  110. ai.addChessman(gridX, gridY, -1);
  111. paintVersion.value++;
  112. if (ai.isWin(gridX, gridY, -1)) {
  113. gameOver.value = true;
  114. TipsDialog.show(Get.context!, '很遗憾', '决策树算法打败了您');
  115. }
  116. }
  117. void undo() {
  118. if (gameOver.value) {
  119. TipsDialog.show(Get.context!, '提示', '对局已结束,请重新开局');
  120. return;
  121. }
  122. if (!_userContext.regretChess()) {
  123. TipsDialog.show(Get.context!, '提示', '现阶段不能悔棋');
  124. return;
  125. }
  126. board.undo();
  127. _rebuildAiFromBoard();
  128. paintVersion.value++;
  129. _refreshStatus();
  130. }
  131. void _rebuildAiFromBoard() {
  132. ai.init();
  133. for (final po in board.state) {
  134. ai.addChessman(
  135. po.gridX,
  136. po.gridY,
  137. po.chess is WhiteChess ? 1 : -1,
  138. );
  139. }
  140. }
  141. Future<void> surrender() async {
  142. if (!_userContext.surrender()) {
  143. await TipsDialog.show(Get.context!, '提示', '现阶段不能投降');
  144. return;
  145. }
  146. final ok = await TipsDialog.showByChoose(
  147. Get.context!,
  148. '提示',
  149. '是否要投降并重新开局?',
  150. '是',
  151. '否',
  152. );
  153. if (ok == true) resetGame();
  154. }
  155. Future<void> restart() async {
  156. final ok = await TipsDialog.showByChoose(
  157. Get.context!,
  158. '提示',
  159. '是否重新开局?',
  160. '是',
  161. '否',
  162. );
  163. if (ok == true) resetGame();
  164. }
  165. void resetGame() {
  166. board.clean();
  167. _userContext.reset();
  168. ai.init();
  169. gameOver.value = false;
  170. paintVersion.value++;
  171. _refreshStatus();
  172. }
  173. }