|
@@ -2,361 +2,330 @@ import 'dart:math';
|
|
|
|
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
-import 'package:gobang/ai/Ai.dart';
|
|
|
|
|
-import 'package:gobang/factory/ThemeFactory.dart';
|
|
|
|
|
-import 'package:gobang/flyweight/Chess.dart';
|
|
|
|
|
-import 'package:gobang/memorandum/Checkerboard.dart';
|
|
|
|
|
-import 'package:gobang/utils/TipsDialog.dart';
|
|
|
|
|
-import 'package:gobang/viewModel/GameViewModel.dart';
|
|
|
|
|
|
|
+import 'package:gobang/ai/ai.dart';
|
|
|
|
|
+import 'package:gobang/bridge/circle_shape.dart';
|
|
|
|
|
+import 'package:gobang/constants.dart';
|
|
|
|
|
+import 'package:gobang/factory/black_theme_factory.dart';
|
|
|
|
|
+import 'package:gobang/factory/blue_theme_factory.dart';
|
|
|
|
|
+import 'package:gobang/factory/theme_factory.dart';
|
|
|
|
|
+import 'package:gobang/flyweight/chess.dart';
|
|
|
|
|
+import 'package:gobang/flyweight/position.dart';
|
|
|
|
|
+import 'package:gobang/memorandum/checkerboard.dart';
|
|
|
|
|
+import 'package:gobang/utils/tips_dialog.dart';
|
|
|
|
|
+import 'package:gobang/viewModel/game_view_model.dart';
|
|
|
|
|
|
|
|
-import 'bridge/CircleShape.dart';
|
|
|
|
|
-import 'factory/BlackThemeFactory.dart';
|
|
|
|
|
-import 'factory/BlueThemeFactory.dart';
|
|
|
|
|
-import 'flyweight/Position.dart';
|
|
|
|
|
-
|
|
|
|
|
-var width = 0.0;
|
|
|
|
|
-
|
|
|
|
|
-///简单的实现五子棋效果
|
|
|
|
|
class HomePage extends StatefulWidget {
|
|
class HomePage extends StatefulWidget {
|
|
|
|
|
+ const HomePage({super.key});
|
|
|
|
|
+
|
|
|
@override
|
|
@override
|
|
|
- State<StatefulWidget> createState() => HomePageState();
|
|
|
|
|
|
|
+ State<HomePage> createState() => HomePageState();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class HomePageState extends State<HomePage> {
|
|
class HomePageState extends State<HomePage> {
|
|
|
- ThemeFactory? _themeFactory;
|
|
|
|
|
- GameViewModel _viewModel = GameViewModel.getInstance();
|
|
|
|
|
- Checkerboard _originator = Checkerboard.getInstance();
|
|
|
|
|
- Icon lightOn = Icon(Icons.lightbulb, color: Colors.amberAccent);
|
|
|
|
|
- Icon lightOff = Icon(Icons.lightbulb_outline_rounded);
|
|
|
|
|
- Icon circle = Icon(Icons.circle_outlined);
|
|
|
|
|
- Icon rect = Icon(Icons.crop_square);
|
|
|
|
|
- Icon? currentLight, currentShape;
|
|
|
|
|
|
|
+ late ThemeFactory _themeFactory;
|
|
|
|
|
+ final GameViewModel _viewModel = GameViewModel.getInstance();
|
|
|
|
|
+ final Checkerboard _board = Checkerboard.getInstance();
|
|
|
|
|
+ final Ai _ai = Ai.getInstance();
|
|
|
|
|
+
|
|
|
|
|
+ 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 _currentLight = _lightOn;
|
|
|
|
|
+ Icon _currentShape = _circleIcon;
|
|
|
|
|
+ bool _useCircle = true;
|
|
|
|
|
+ bool _gameOver = false;
|
|
|
|
|
+ int _paintVersion = 0;
|
|
|
|
|
+
|
|
|
|
|
+ double get _boardWidth => MediaQuery.of(context).size.width * 0.8;
|
|
|
|
|
+
|
|
|
|
|
+ double get _cellSize => _boardWidth / (kBoardSize - 1);
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
void initState() {
|
|
void initState() {
|
|
|
- currentLight = lightOn;
|
|
|
|
|
- _themeFactory = BlueThemeFactory();
|
|
|
|
|
- currentShape = circle;
|
|
|
|
|
super.initState();
|
|
super.initState();
|
|
|
|
|
+ _themeFactory = BlueThemeFactory();
|
|
|
|
|
+ _ai.init();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
|
- width = MediaQuery.of(context).size.width * 0.8;
|
|
|
|
|
|
|
+ final themeColor = _themeFactory.getTheme().getThemeColor();
|
|
|
|
|
|
|
|
return Scaffold(
|
|
return Scaffold(
|
|
|
appBar: AppBar(
|
|
appBar: AppBar(
|
|
|
elevation: 0,
|
|
elevation: 0,
|
|
|
- backgroundColor: _themeFactory!.getTheme().getThemeColor(),
|
|
|
|
|
- title: Text("南瓜五子棋"),
|
|
|
|
|
|
|
+ backgroundColor: themeColor,
|
|
|
|
|
+ title: const Text('南瓜五子棋'),
|
|
|
actions: [
|
|
actions: [
|
|
|
IconButton(
|
|
IconButton(
|
|
|
- onPressed: () {
|
|
|
|
|
- setState(() {
|
|
|
|
|
- if (_themeFactory is BlackThemeFactory) {
|
|
|
|
|
- currentLight = lightOn;
|
|
|
|
|
- _themeFactory = BlueThemeFactory();
|
|
|
|
|
- } else {
|
|
|
|
|
- currentLight = lightOff;
|
|
|
|
|
- _themeFactory = BlackThemeFactory();
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- },
|
|
|
|
|
- icon: currentLight!),
|
|
|
|
|
|
|
+ onPressed: _toggleTheme,
|
|
|
|
|
+ icon: _currentLight,
|
|
|
|
|
+ ),
|
|
|
IconButton(
|
|
IconButton(
|
|
|
- onPressed: () {
|
|
|
|
|
- setState(() {
|
|
|
|
|
- if (currentShape == circle) {
|
|
|
|
|
- currentShape = rect;
|
|
|
|
|
- } else {
|
|
|
|
|
- currentShape = circle;
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- },
|
|
|
|
|
- icon: currentShape!),
|
|
|
|
|
|
|
+ onPressed: _toggleShape,
|
|
|
|
|
+ icon: _currentShape,
|
|
|
|
|
+ ),
|
|
|
],
|
|
],
|
|
|
),
|
|
),
|
|
|
body: Container(
|
|
body: Container(
|
|
|
decoration: BoxDecoration(
|
|
decoration: BoxDecoration(
|
|
|
- gradient: LinearGradient(
|
|
|
|
|
- colors: [
|
|
|
|
|
- _themeFactory!.getTheme().getThemeColor(),
|
|
|
|
|
- Colors.white,
|
|
|
|
|
- ],
|
|
|
|
|
- stops: [
|
|
|
|
|
- 0.0,
|
|
|
|
|
- 1
|
|
|
|
|
- ],
|
|
|
|
|
- begin: FractionalOffset.topCenter,
|
|
|
|
|
- end: FractionalOffset.bottomCenter,
|
|
|
|
|
- tileMode: TileMode.repeated)),
|
|
|
|
|
|
|
+ gradient: LinearGradient(
|
|
|
|
|
+ colors: [themeColor, Colors.white],
|
|
|
|
|
+ begin: Alignment.topCenter,
|
|
|
|
|
+ end: Alignment.bottomCenter,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
child: Center(
|
|
child: Center(
|
|
|
child: Column(
|
|
child: Column(
|
|
|
- mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
- crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
- mainAxisSize: MainAxisSize.max,
|
|
|
|
|
- children: <Widget>[
|
|
|
|
|
- Padding(
|
|
|
|
|
- padding: EdgeInsets.only(top: 14, bottom: 30),
|
|
|
|
|
- child: Text(
|
|
|
|
|
- _viewModel.state,
|
|
|
|
|
- style: TextStyle(color: Colors.white),
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Padding(
|
|
|
|
|
+ padding: const EdgeInsets.only(top: 14, bottom: 30),
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ _viewModel.state,
|
|
|
|
|
+ style: const TextStyle(color: Colors.white),
|
|
|
),
|
|
),
|
|
|
- GestureDetector(
|
|
|
|
|
- onTapDown: (topDownDetails) {
|
|
|
|
|
- var position = topDownDetails.localPosition;
|
|
|
|
|
- Chess chess = _viewModel.play(currentShape == circle);
|
|
|
|
|
- setState(() {
|
|
|
|
|
- ChessPainter._position =
|
|
|
|
|
- Position(position.dx, position.dy, chess);
|
|
|
|
|
- });
|
|
|
|
|
- },
|
|
|
|
|
- child: Stack(
|
|
|
|
|
- children: [
|
|
|
|
|
- CustomPaint(
|
|
|
|
|
- size: Size(width, width),
|
|
|
|
|
- painter: CheckerBoardPainter(),
|
|
|
|
|
- ),
|
|
|
|
|
- CustomPaint(
|
|
|
|
|
- size: Size(width, width),
|
|
|
|
|
- painter: ChessPainter(turnAi),
|
|
|
|
|
- )
|
|
|
|
|
- ],
|
|
|
|
|
- )),
|
|
|
|
|
- Padding(
|
|
|
|
|
- padding: const EdgeInsets.only(top: 16.0),
|
|
|
|
|
- child: Row(
|
|
|
|
|
- mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
- children: [
|
|
|
|
|
- IconButton(
|
|
|
|
|
- onPressed: () {
|
|
|
|
|
- if (_viewModel.undo()) {
|
|
|
|
|
- _originator.undo();
|
|
|
|
|
- Ai.getInstance().init();
|
|
|
|
|
- for (Position po in _originator.state) {
|
|
|
|
|
- Ai.getInstance().addChessman(
|
|
|
|
|
- po.dx ~/ (width / 15),
|
|
|
|
|
- po.dy ~/ (width / 15),
|
|
|
|
|
- po.chess is WhiteChess ? 1 : -1);
|
|
|
|
|
- }
|
|
|
|
|
- setState(() {});
|
|
|
|
|
- } else {
|
|
|
|
|
- TipsDialog.show(context, "提示", "现阶段不能悔棋");
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- icon: Icon(Icons.undo)),
|
|
|
|
|
- IconButton(
|
|
|
|
|
- onPressed: () {
|
|
|
|
|
- if (_viewModel.surrender()) {
|
|
|
|
|
- TipsDialog.showByChoose(
|
|
|
|
|
- context, "提示", "是否要投降并重新开局?", "是", "否",
|
|
|
|
|
- (value) {
|
|
|
|
|
- if (value) {
|
|
|
|
|
- setState(() {
|
|
|
|
|
- ChessPainter._position = null;
|
|
|
|
|
- _originator.clean();
|
|
|
|
|
- _viewModel.reset();
|
|
|
|
|
- Ai.getInstance().init();
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- Navigator.pop(context);
|
|
|
|
|
- });
|
|
|
|
|
- } else {
|
|
|
|
|
- TipsDialog.show(context, "提示", "现阶段不能投降");
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- icon: Icon(
|
|
|
|
|
- Icons.sports_handball,
|
|
|
|
|
- color: Colors.deepPurple,
|
|
|
|
|
- )),
|
|
|
|
|
- IconButton(
|
|
|
|
|
- onPressed: () {
|
|
|
|
|
- TipsDialog.showByChoose(
|
|
|
|
|
- context, "提示", "是否重新开局?", "是", "否", (value) {
|
|
|
|
|
- if (value) {
|
|
|
|
|
- setState(() {
|
|
|
|
|
- ChessPainter._position = null;
|
|
|
|
|
- _originator.clean();
|
|
|
|
|
- _viewModel.reset();
|
|
|
|
|
- Ai.getInstance().init();
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- Navigator.pop(context);
|
|
|
|
|
- });
|
|
|
|
|
- },
|
|
|
|
|
- icon: Icon(
|
|
|
|
|
- Icons.restart_alt,
|
|
|
|
|
- color: Colors.indigo,
|
|
|
|
|
- )),
|
|
|
|
|
- ],
|
|
|
|
|
|
|
+ ),
|
|
|
|
|
+ GestureDetector(
|
|
|
|
|
+ onTapDown: _onBoardTap,
|
|
|
|
|
+ child: SizedBox(
|
|
|
|
|
+ width: _boardWidth,
|
|
|
|
|
+ height: _boardWidth,
|
|
|
|
|
+ child: CustomPaint(
|
|
|
|
|
+ size: Size.square(_boardWidth),
|
|
|
|
|
+ painter: BoardPainter(
|
|
|
|
|
+ pieces: List.unmodifiable(_board.state),
|
|
|
|
|
+ version: _paintVersion,
|
|
|
|
|
+ ),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
- ]),
|
|
|
|
|
|
|
+ ),
|
|
|
|
|
+ Padding(
|
|
|
|
|
+ padding: const EdgeInsets.only(top: 16),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ IconButton(
|
|
|
|
|
+ onPressed: _onUndo,
|
|
|
|
|
+ icon: const Icon(Icons.undo),
|
|
|
|
|
+ ),
|
|
|
|
|
+ IconButton(
|
|
|
|
|
+ onPressed: _onSurrender,
|
|
|
|
|
+ icon: const Icon(
|
|
|
|
|
+ Icons.sports_handball,
|
|
|
|
|
+ color: Colors.deepPurple,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ IconButton(
|
|
|
|
|
+ onPressed: _onRestart,
|
|
|
|
|
+ icon: const Icon(
|
|
|
|
|
+ Icons.restart_alt,
|
|
|
|
|
+ color: Colors.indigo,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// Ai 下棋
|
|
|
|
|
- void turnAi() {
|
|
|
|
|
- if (ChessPainter._position!.chess is WhiteChess &&
|
|
|
|
|
- Ai.getInstance().isWin(ChessPainter._position!.dx ~/ (width / 15),
|
|
|
|
|
- ChessPainter._position!.dy ~/ (width / 15), 1)) {
|
|
|
|
|
- TipsDialog.show(context, "恭喜", "您打败了决策树算法");
|
|
|
|
|
- }
|
|
|
|
|
- // 获取Ai下棋地址
|
|
|
|
|
- Ai ai = Ai.getInstance();
|
|
|
|
|
- ChessPainter._position = ai.searchPosition();
|
|
|
|
|
- // 设置棋子外观
|
|
|
|
|
- ChessPainter._position!.chess.chessShape = CircleShape();
|
|
|
|
|
- // 加入决策中
|
|
|
|
|
- Ai.getInstance().addChessman(ChessPainter._position!.dx.toInt(),
|
|
|
|
|
- ChessPainter._position!.dy.toInt(), -1);
|
|
|
|
|
- if (ChessPainter._position!.chess is BlackChess &&
|
|
|
|
|
- Ai.getInstance().isWin(ChessPainter._position!.dx.toInt(),
|
|
|
|
|
- ChessPainter._position!.dy.toInt(), -1)) {
|
|
|
|
|
- TipsDialog.show(context, "很遗憾", "决策树算法打败了您");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ void _toggleTheme() {
|
|
|
setState(() {
|
|
setState(() {
|
|
|
- ChessPainter._position!.dx = ChessPainter._position!.dx * (width / 15);
|
|
|
|
|
- ChessPainter._position!.dy = ChessPainter._position!.dy * (width / 15);
|
|
|
|
|
|
|
+ if (_themeFactory is BlackThemeFactory) {
|
|
|
|
|
+ _currentLight = _lightOn;
|
|
|
|
|
+ _themeFactory = BlueThemeFactory();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ _currentLight = _lightOff;
|
|
|
|
|
+ _themeFactory = BlackThemeFactory();
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-class ChessPainter extends CustomPainter {
|
|
|
|
|
- static Position? _position;
|
|
|
|
|
- final Function _function;
|
|
|
|
|
- Checkerboard _originator = Checkerboard.getInstance();
|
|
|
|
|
|
|
+ void _toggleShape() {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _useCircle = !_useCircle;
|
|
|
|
|
+ _currentShape = _useCircle ? _circleIcon : _rectIcon;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- ChessPainter(Function f) : _function = f;
|
|
|
|
|
|
|
+ void _onBoardTap(TapDownDetails details) {
|
|
|
|
|
+ if (_gameOver) return;
|
|
|
|
|
|
|
|
- @override
|
|
|
|
|
- void paint(Canvas canvas, Size size) {
|
|
|
|
|
- if (_position == null) {
|
|
|
|
|
|
|
+ final snap = _snapToGrid(details.localPosition);
|
|
|
|
|
+ if (snap == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ final (gridX, gridY, px, py) = snap;
|
|
|
|
|
+ if (!_ai.isLegal(gridX, gridY)) return;
|
|
|
|
|
+
|
|
|
|
|
+ final chess = _viewModel.play();
|
|
|
|
|
+ final shape = _viewModel.shapeFor(_useCircle);
|
|
|
|
|
+ final position = Position(px, py, chess, chessShape: shape);
|
|
|
|
|
+
|
|
|
|
|
+ _board.add(position);
|
|
|
|
|
+ _ai.addChessman(gridX, gridY, 1);
|
|
|
|
|
+ _paintVersion++;
|
|
|
|
|
+
|
|
|
|
|
+ if (_ai.isWin(gridX, gridY, 1)) {
|
|
|
|
|
+ setState(() => _gameOver = true);
|
|
|
|
|
+ TipsDialog.show(context, '恭喜', '您打败了决策树算法');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- bool add = false;
|
|
|
|
|
- double mWidth = size.width / 15; // 每行/列 15 个
|
|
|
|
|
- double mHeight = size.height / 15;
|
|
|
|
|
- var mPaint = Paint();
|
|
|
|
|
- //求两个点之间的距离,让棋子正确的显示在坐标轴上面
|
|
|
|
|
- var dx = _position!.dx;
|
|
|
|
|
- var dy = _position!.dy;
|
|
|
|
|
- for (int i = 0; i < CheckerBoardPainter._crossOverBeanList.length; i++) {
|
|
|
|
|
- var absX =
|
|
|
|
|
- (dx - CheckerBoardPainter._crossOverBeanList[i]._dx).abs(); //两个点的x轴距离
|
|
|
|
|
- var absY =
|
|
|
|
|
- (dy - CheckerBoardPainter._crossOverBeanList[i]._dy).abs(); //两个点的y轴距离
|
|
|
|
|
- var s = sqrt(absX * absX +
|
|
|
|
|
- absY * absY); //利用直角三角形求斜边公式(a的平方 + b的平方 = c的平方)来计算出两点间的距离
|
|
|
|
|
- if (s <= mWidth / 2 - 2) {
|
|
|
|
|
- // 触摸点到棋盘坐标坐标点距离小于等于棋子半径,那么
|
|
|
|
|
- //找到离触摸点最近的棋盘坐标点并记录保存下来
|
|
|
|
|
- _position!.dx = CheckerBoardPainter._crossOverBeanList[i]._dx;
|
|
|
|
|
- _position!.dy = CheckerBoardPainter._crossOverBeanList[i]._dy;
|
|
|
|
|
- _originator.add(_position!);
|
|
|
|
|
- add = true;
|
|
|
|
|
- if (_position!.chess is WhiteChess) {
|
|
|
|
|
- Ai.getInstance().addChessman(
|
|
|
|
|
- _position!.dx ~/ (width / 15), _position!.dy ~/ (width / 15), 1);
|
|
|
|
|
- }
|
|
|
|
|
- // flag = false; //白子下完了,该黑子下了
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ _playAi();
|
|
|
|
|
+ setState(() {});
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 将触摸点吸附到最近交叉点;过远则返回 null
|
|
|
|
|
+ (int, int, double, double)? _snapToGrid(Offset local) {
|
|
|
|
|
+ final cell = _cellSize;
|
|
|
|
|
+ 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() {
|
|
|
|
|
+ final raw = _ai.searchPosition();
|
|
|
|
|
+ if (raw.dx < 0 || raw.dy < 0) return;
|
|
|
|
|
+
|
|
|
|
|
+ final gridX = raw.dx.toInt();
|
|
|
|
|
+ final gridY = raw.dy.toInt();
|
|
|
|
|
+ final cell = _cellSize;
|
|
|
|
|
+ final position = Position(
|
|
|
|
|
+ gridX * cell,
|
|
|
|
|
+ gridY * cell,
|
|
|
|
|
+ raw.chess,
|
|
|
|
|
+ chessShape: CircleShape(),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ _board.add(position);
|
|
|
|
|
+ _ai.addChessman(gridX, gridY, -1);
|
|
|
|
|
+ _paintVersion++;
|
|
|
|
|
+
|
|
|
|
|
+ if (_ai.isWin(gridX, gridY, -1)) {
|
|
|
|
|
+ _gameOver = true;
|
|
|
|
|
+ TipsDialog.show(context, '很遗憾', '决策树算法打败了您');
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- //画子
|
|
|
|
|
- mPaint..style = PaintingStyle.fill;
|
|
|
|
|
- if (_originator.state.isNotEmpty) {
|
|
|
|
|
- for (int i = 0; i < _originator.state.length; i++) {
|
|
|
|
|
- mPaint..color = _originator.state[i].chess.color;
|
|
|
|
|
- if (_originator.state[i].chess.chessShape.shape == 1) {
|
|
|
|
|
- canvas.drawCircle(
|
|
|
|
|
- Offset(_originator.state[i].dx, _originator.state[i].dy),
|
|
|
|
|
- min(mWidth / 2, mHeight / 2) - 2,
|
|
|
|
|
- mPaint);
|
|
|
|
|
- }
|
|
|
|
|
- if (_originator.state[i].chess.chessShape.shape == 2) {
|
|
|
|
|
- Rect rect = Rect.fromCircle(
|
|
|
|
|
- center: Offset(_originator.state[i].dx, _originator.state[i].dy),
|
|
|
|
|
- radius: min(mWidth / 2, mHeight / 2) - 2);
|
|
|
|
|
- canvas.drawRect(rect, mPaint);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ void _rebuildAiFromBoard() {
|
|
|
|
|
+ _ai.init();
|
|
|
|
|
+ final cell = _cellSize;
|
|
|
|
|
+ for (final po in _board.state) {
|
|
|
|
|
+ final gx = (po.dx / cell).round();
|
|
|
|
|
+ final gy = (po.dy / cell).round();
|
|
|
|
|
+ _ai.addChessman(gx, gy, po.chess is WhiteChess ? 1 : -1);
|
|
|
}
|
|
}
|
|
|
- WidgetsBinding.instance!.addPostFrameCallback((_) {
|
|
|
|
|
- if (add && _position!.chess is WhiteChess) {
|
|
|
|
|
- _function();
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //在实际场景中正确利用此回调可以避免重绘开销,本示例我们简单的返回true
|
|
|
|
|
- @override
|
|
|
|
|
- bool shouldRepaint(CustomPainter oldDelegate) {
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ void _onUndo() {
|
|
|
|
|
+ if (_gameOver) {
|
|
|
|
|
+ TipsDialog.show(context, '提示', '对局已结束,请重新开局');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!_viewModel.undo()) {
|
|
|
|
|
+ TipsDialog.show(context, '提示', '现阶段不能悔棋');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ _board.undo();
|
|
|
|
|
+ _rebuildAiFromBoard();
|
|
|
|
|
+ setState(() => _paintVersion++);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Future<void> _onSurrender() async {
|
|
|
|
|
+ if (!_viewModel.surrender()) {
|
|
|
|
|
+ await TipsDialog.show(context, '提示', '现阶段不能投降');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ final ok = await TipsDialog.showByChoose(
|
|
|
|
|
+ context,
|
|
|
|
|
+ '提示',
|
|
|
|
|
+ '是否要投降并重新开局?',
|
|
|
|
|
+ '是',
|
|
|
|
|
+ '否',
|
|
|
|
|
+ );
|
|
|
|
|
+ if (ok == true && mounted) {
|
|
|
|
|
+ _resetGame();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Future<void> _onRestart() async {
|
|
|
|
|
+ final ok = await TipsDialog.showByChoose(
|
|
|
|
|
+ context,
|
|
|
|
|
+ '提示',
|
|
|
|
|
+ '是否重新开局?',
|
|
|
|
|
+ '是',
|
|
|
|
|
+ '否',
|
|
|
|
|
+ );
|
|
|
|
|
+ if (ok == true && mounted) {
|
|
|
|
|
+ _resetGame();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void _resetGame() {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _board.clean();
|
|
|
|
|
+ _viewModel.reset();
|
|
|
|
|
+ _ai.init();
|
|
|
|
|
+ _gameOver = false;
|
|
|
|
|
+ _paintVersion++;
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-class CheckerBoardPainter extends CustomPainter {
|
|
|
|
|
- static List<CrossOverBean> _crossOverBeanList = [];
|
|
|
|
|
- static int _state = 0;
|
|
|
|
|
|
|
+/// 棋盘 + 棋子同层绘制,避免双重 CustomPaint 与 paint 内副作用
|
|
|
|
|
+class BoardPainter extends CustomPainter {
|
|
|
|
|
+ BoardPainter({required this.pieces, required this.version});
|
|
|
|
|
+
|
|
|
|
|
+ final List<Position> pieces;
|
|
|
|
|
+ final int version;
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
void paint(Canvas canvas, Size size) {
|
|
void paint(Canvas canvas, Size size) {
|
|
|
- double mWidth = size.width / 15;
|
|
|
|
|
- double mHeight = size.height / 15;
|
|
|
|
|
- var mPaint = Paint();
|
|
|
|
|
-
|
|
|
|
|
- _crossOverBeanList.clear();
|
|
|
|
|
- //重绘下整个界面的画布北京颜色
|
|
|
|
|
- //设置画笔,画棋盘背景
|
|
|
|
|
- mPaint
|
|
|
|
|
- ..isAntiAlias = true //抗锯齿
|
|
|
|
|
- ..style = PaintingStyle.fill //填充
|
|
|
|
|
- ..color = Color(0x77cdb175); //背景为纸黄色
|
|
|
|
|
- canvas.drawRect(
|
|
|
|
|
- Rect.fromCenter(
|
|
|
|
|
- center: Offset(size.width / 2, size.height / 2),
|
|
|
|
|
- width: size.width,
|
|
|
|
|
- height: size.height),
|
|
|
|
|
- mPaint);
|
|
|
|
|
- //画棋盘网格
|
|
|
|
|
- mPaint
|
|
|
|
|
|
|
+ final cell = size.width / (kBoardSize - 1);
|
|
|
|
|
+ final bg = Paint()
|
|
|
|
|
+ ..isAntiAlias = true
|
|
|
|
|
+ ..style = PaintingStyle.fill
|
|
|
|
|
+ ..color = const Color(0x77cdb175);
|
|
|
|
|
+ canvas.drawRect(Offset.zero & size, bg);
|
|
|
|
|
+
|
|
|
|
|
+ final line = Paint()
|
|
|
..style = PaintingStyle.stroke
|
|
..style = PaintingStyle.stroke
|
|
|
..color = CupertinoColors.systemGrey6
|
|
..color = CupertinoColors.systemGrey6
|
|
|
- ..strokeWidth = 1.0;
|
|
|
|
|
- for (var i = 0; i <= 15; i++) {
|
|
|
|
|
- //画横线
|
|
|
|
|
- canvas.drawLine(
|
|
|
|
|
- Offset(0, mHeight * i), Offset(size.width, mHeight * i), mPaint);
|
|
|
|
|
|
|
+ ..strokeWidth = 1;
|
|
|
|
|
+ for (var i = 0; i < kBoardSize; i++) {
|
|
|
|
|
+ final o = cell * i;
|
|
|
|
|
+ canvas.drawLine(Offset(0, o), Offset(size.width, o), line);
|
|
|
|
|
+ canvas.drawLine(Offset(o, 0), Offset(o, size.height), line);
|
|
|
}
|
|
}
|
|
|
- for (var i = 0; i <= 15; i++) {
|
|
|
|
|
- //画竖线
|
|
|
|
|
- canvas.drawLine(
|
|
|
|
|
- Offset(mWidth * i, 0), Offset(mWidth * i, size.height), mPaint);
|
|
|
|
|
- }
|
|
|
|
|
- //记录横竖线所有的交叉点
|
|
|
|
|
- for (int i = 0; i <= 15; i++) {
|
|
|
|
|
- for (int j = 0; j <= 15; j++) {
|
|
|
|
|
- _crossOverBeanList.add(CrossOverBean(mWidth * j, mHeight * i));
|
|
|
|
|
|
|
+
|
|
|
|
|
+ final radius = cell / 2 - 2;
|
|
|
|
|
+ final fill = Paint()..style = PaintingStyle.fill;
|
|
|
|
|
+ for (final p in pieces) {
|
|
|
|
|
+ fill.color = p.chess.color;
|
|
|
|
|
+ final center = Offset(p.dx, p.dy);
|
|
|
|
|
+ if (p.chessShape.shape == 1) {
|
|
|
|
|
+ canvas.drawCircle(center, radius, fill);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ canvas.drawRect(
|
|
|
|
|
+ Rect.fromCircle(center: center, radius: radius),
|
|
|
|
|
+ fill,
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //在实际场景中正确利用此回调可以避免重绘开销,本示例我们简单的返回true
|
|
|
|
|
@override
|
|
@override
|
|
|
- bool shouldRepaint(CustomPainter oldDelegate) {
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ bool shouldRepaint(covariant BoardPainter oldDelegate) {
|
|
|
|
|
+ return oldDelegate.version != version;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-///记录棋盘上横竖线的交叉点
|
|
|
|
|
-class CrossOverBean {
|
|
|
|
|
- double _dx;
|
|
|
|
|
- double _dy;
|
|
|
|
|
-
|
|
|
|
|
- CrossOverBean(this._dx, this._dy);
|
|
|
|
|
-}
|
|
|