| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- import 'dart:math';
- import 'package:flutter/material.dart';
- import 'package:get/get.dart';
- import 'package:gobang/models/chess.dart';
- import 'package:gobang/models/checkerboard.dart';
- import 'package:gobang/models/chess_shape.dart';
- import 'package:gobang/models/circle_shape.dart';
- import 'package:gobang/models/game_state.dart';
- import 'package:gobang/models/position.dart';
- import 'package:gobang/models/rect_shape.dart';
- import 'package:gobang/models/user_context.dart';
- import 'package:gobang/services/ai_service.dart';
- import 'package:gobang/services/chess_flyweight_factory.dart';
- import 'package:gobang/themes/black_theme_factory.dart';
- import 'package:gobang/themes/blue_theme_factory.dart';
- import 'package:gobang/themes/theme_factory.dart';
- import 'package:gobang/utils/constants.dart';
- import 'package:gobang/utils/tips_dialog.dart';
- /// 对局控制器:棋盘交互、AI、主题与阶段状态
- class GameController extends GetxController {
- final UserContext _userContext = UserContext();
- final Checkerboard board = Checkerboard.getInstance();
- final AiService ai = AiService.getInstance();
- final themeFactory = Rx<ThemeFactory>(BlueThemeFactory());
- final useCircle = true.obs;
- final gameOver = false.obs;
- final paintVersion = 0.obs;
- final statusText = '热身阶段,不能悔棋,不能投降'.obs;
- static const lightOn = Icon(Icons.lightbulb, color: Colors.amberAccent);
- static const lightOff = Icon(Icons.lightbulb_outline_rounded);
- static const circleIcon = Icon(Icons.circle_outlined);
- static const rectIcon = Icon(Icons.crop_square);
- Icon get currentLight =>
- themeFactory.value is BlackThemeFactory ? lightOff : lightOn;
- Icon get currentShape => useCircle.value ? circleIcon : rectIcon;
- Color get themeColor => themeFactory.value.getTheme().getThemeColor();
- @override
- void onInit() {
- super.onInit();
- ai.init();
- _refreshStatus();
- }
- void toggleTheme() {
- themeFactory.value = themeFactory.value is BlackThemeFactory
- ? BlueThemeFactory()
- : BlackThemeFactory();
- }
- void toggleShape() => useCircle.toggle();
- ChessShape _shapeFor(bool circle) =>
- circle ? CircleShape() : RectShape();
- void _refreshStatus() {
- final s = _userContext.state;
- if (s is StartState) {
- statusText.value = '热身阶段,不能悔棋,不能投降';
- } else if (s is MidState) {
- statusText.value = '入神阶段,可以悔棋且剩余${3 - s.reg}次,可以投降';
- } else if (s is EndState) {
- statusText.value = '白热化阶段,悔棋次数已用完,但可以投降';
- }
- }
- void onBoardTap(Offset local, double boardWidth) {
- if (gameOver.value) return;
- final cell = boardWidth / (kBoardSize - 1);
- final snap = _snapToGrid(local, cell);
- if (snap == null) return;
- final (gridX, gridY, px, py) = snap;
- if (!ai.isLegal(gridX, gridY)) return;
- _userContext.play();
- final chess = ChessFlyweightFactory.getInstance().getChess('white');
- board.add(Position(
- px,
- py,
- chess,
- gridX: gridX,
- gridY: gridY,
- chessShape: _shapeFor(useCircle.value),
- ));
- ai.addChessman(gridX, gridY, 1);
- paintVersion.value++;
- _refreshStatus();
- if (ai.isWin(gridX, gridY, 1)) {
- gameOver.value = true;
- TipsDialog.show(Get.context!, '恭喜', '您打败了决策树算法');
- return;
- }
- _playAi(cell);
- }
- (int, int, double, double)? _snapToGrid(Offset local, double cell) {
- final gx = (local.dx / cell).round().clamp(0, kBoardSize - 1);
- final gy = (local.dy / cell).round().clamp(0, kBoardSize - 1);
- final px = gx * cell;
- final py = gy * cell;
- final dist = sqrt(pow(local.dx - px, 2) + pow(local.dy - py, 2));
- if (dist > cell / 2 - 2) return null;
- return (gx, gy, px, py);
- }
- void _playAi(double cell) {
- final raw = ai.searchPosition();
- if (raw.dx < 0 || raw.dy < 0) return;
- final gridX = raw.dx.toInt();
- final gridY = raw.dy.toInt();
- board.add(Position(
- gridX * cell,
- gridY * cell,
- raw.chess,
- gridX: gridX,
- gridY: gridY,
- chessShape: CircleShape(),
- ));
- ai.addChessman(gridX, gridY, -1);
- paintVersion.value++;
- if (ai.isWin(gridX, gridY, -1)) {
- gameOver.value = true;
- TipsDialog.show(Get.context!, '很遗憾', '决策树算法打败了您');
- }
- }
- void undo() {
- if (gameOver.value) {
- TipsDialog.show(Get.context!, '提示', '对局已结束,请重新开局');
- return;
- }
- if (!_userContext.regretChess()) {
- TipsDialog.show(Get.context!, '提示', '现阶段不能悔棋');
- return;
- }
- board.undo();
- _rebuildAiFromBoard();
- paintVersion.value++;
- _refreshStatus();
- }
- void _rebuildAiFromBoard() {
- ai.init();
- for (final po in board.state) {
- ai.addChessman(
- po.gridX,
- po.gridY,
- po.chess is WhiteChess ? 1 : -1,
- );
- }
- }
- Future<void> surrender() async {
- if (!_userContext.surrender()) {
- await TipsDialog.show(Get.context!, '提示', '现阶段不能投降');
- return;
- }
- final ok = await TipsDialog.showByChoose(
- Get.context!,
- '提示',
- '是否要投降并重新开局?',
- '是',
- '否',
- );
- if (ok == true) resetGame();
- }
- Future<void> restart() async {
- final ok = await TipsDialog.showByChoose(
- Get.context!,
- '提示',
- '是否重新开局?',
- '是',
- '否',
- );
- if (ok == true) resetGame();
- }
- void resetGame() {
- board.clean();
- _userContext.reset();
- ai.init();
- gameOver.value = false;
- paintVersion.value++;
- _refreshStatus();
- }
- }
|