home_page.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import 'dart:math';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:gobang/ai/Ai.dart';
  5. import 'package:gobang/flyweight/Chess.dart';
  6. import 'package:gobang/memorandum/Checkerboard.dart';
  7. import 'package:gobang/model/BlackTheme.dart';
  8. import 'package:gobang/model/BlueTheme.dart';
  9. import 'package:gobang/model/base_theme.dart';
  10. import 'package:gobang/utils/TipsDialog.dart';
  11. import 'package:gobang/viewModel/GameViewModel.dart';
  12. import '../bridge/CircleShape.dart';
  13. import '../flyweight/Position.dart';
  14. var width = 0.0;
  15. /// Description: home page
  16. /// Time : 02/20/2024 Tuesday
  17. /// Author : liuyuqi.gov@msn.cn
  18. class HomePage extends StatefulWidget {
  19. const HomePage({Key? key}) : super(key: key);
  20. @override
  21. State<StatefulWidget> createState() => HomePageState();
  22. }
  23. class HomePageState extends State<HomePage> {
  24. BaseTheme? _theme;
  25. GameViewModel _viewModel = GameViewModel.getInstance();
  26. Checkerboard _originator = Checkerboard.getInstance();
  27. Icon lightOn = Icon(Icons.lightbulb, color: Colors.amberAccent);
  28. Icon lightOff = Icon(Icons.lightbulb_outline_rounded);
  29. Icon circle = Icon(Icons.circle_outlined);
  30. Icon rect = Icon(Icons.crop_square);
  31. Icon? currentLight, currentShape;
  32. @override
  33. void initState() {
  34. currentLight = lightOn;
  35. _theme = BlueTheme();
  36. currentShape = circle;
  37. super.initState();
  38. }
  39. @override
  40. Widget build(BuildContext context) {
  41. width = MediaQuery.of(context).size.width * 0.8;
  42. return Scaffold(
  43. appBar: AppBar(
  44. elevation: 0,
  45. backgroundColor: _theme!.getThemeColor(),
  46. title: Text("南瓜五子棋"),
  47. actions: [
  48. IconButton(
  49. onPressed: () {
  50. setState(() {
  51. if (_theme is BlackTheme) {
  52. currentLight = lightOn;
  53. _theme = BlueTheme();
  54. } else {
  55. currentLight = lightOff;
  56. _theme = BlackTheme();
  57. }
  58. });
  59. },
  60. icon: currentLight!),
  61. IconButton(
  62. onPressed: () {
  63. setState(() {
  64. if (currentShape == circle) {
  65. currentShape = rect;
  66. } else {
  67. currentShape = circle;
  68. }
  69. });
  70. },
  71. icon: currentShape!),
  72. ],
  73. ),
  74. body: Container(
  75. decoration: BoxDecoration(
  76. gradient: LinearGradient(
  77. colors: [
  78. _theme!.getThemeColor(),
  79. Colors.white,
  80. ],
  81. stops: [
  82. 0.0,
  83. 1
  84. ],
  85. begin: FractionalOffset.topCenter,
  86. end: FractionalOffset.bottomCenter,
  87. tileMode: TileMode.repeated)),
  88. child: Center(
  89. child: Column(
  90. mainAxisAlignment: MainAxisAlignment.center,
  91. crossAxisAlignment: CrossAxisAlignment.center,
  92. mainAxisSize: MainAxisSize.max,
  93. children: <Widget>[
  94. Padding(
  95. padding: EdgeInsets.only(top: 14, bottom: 30),
  96. child: Text(
  97. _viewModel.state,
  98. style: TextStyle(color: Colors.white),
  99. ),
  100. ),
  101. GestureDetector(
  102. onTapDown: (topDownDetails) {
  103. var position = topDownDetails.localPosition;
  104. Chess chess = _viewModel.play(currentShape == circle);
  105. setState(() {
  106. ChessPainter._position =
  107. Position(position.dx, position.dy, chess);
  108. });
  109. },
  110. child: Stack(
  111. children: [
  112. CustomPaint(
  113. size: Size(width, width),
  114. painter: CheckerBoardPainter(),
  115. ),
  116. CustomPaint(
  117. size: Size(width, width),
  118. painter: ChessPainter(turnAi),
  119. )
  120. ],
  121. )),
  122. Padding(
  123. padding: const EdgeInsets.only(top: 16.0),
  124. child: Row(
  125. mainAxisAlignment: MainAxisAlignment.center,
  126. children: [
  127. IconButton(
  128. onPressed: () {
  129. if (_viewModel.undo()) {
  130. _originator.undo();
  131. Ai.getInstance().init();
  132. for (Position po in _originator.state) {
  133. Ai.getInstance().addChessman(
  134. po.dx ~/ (width / 15),
  135. po.dy ~/ (width / 15),
  136. po.chess is WhiteChess ? 1 : -1);
  137. }
  138. setState(() {});
  139. } else {
  140. TipsDialog.show(context, "提示", "现阶段不能悔棋");
  141. }
  142. },
  143. icon: Icon(Icons.undo)),
  144. IconButton(
  145. onPressed: () {
  146. if (_viewModel.surrender()) {
  147. TipsDialog.showByChoose(
  148. context, "提示", "是否要投降并重新开局?", "是", "否",
  149. (value) {
  150. if (value) {
  151. setState(() {
  152. ChessPainter._position = null;
  153. _originator.clean();
  154. _viewModel.reset();
  155. Ai.getInstance().init();
  156. });
  157. }
  158. Navigator.pop(context);
  159. });
  160. } else {
  161. TipsDialog.show(context, "提示", "现阶段不能投降");
  162. }
  163. },
  164. icon: Icon(
  165. Icons.sports_handball,
  166. color: Colors.deepPurple,
  167. )),
  168. IconButton(
  169. onPressed: () {
  170. TipsDialog.showByChoose(
  171. context, "提示", "是否重新开局?", "是", "否", (value) {
  172. if (value) {
  173. setState(() {
  174. ChessPainter._position = null;
  175. _originator.clean();
  176. _viewModel.reset();
  177. Ai.getInstance().init();
  178. });
  179. }
  180. Navigator.pop(context);
  181. });
  182. },
  183. icon: Icon(
  184. Icons.restart_alt,
  185. color: Colors.indigo,
  186. )),
  187. ],
  188. ),
  189. ),
  190. ]),
  191. ),
  192. ),
  193. );
  194. }
  195. /// Ai 下棋
  196. void turnAi() {
  197. if (ChessPainter._position!.chess is WhiteChess &&
  198. Ai.getInstance().isWin(ChessPainter._position!.dx ~/ (width / 15),
  199. ChessPainter._position!.dy ~/ (width / 15), 1)) {
  200. TipsDialog.show(context, "恭喜", "您打败了决策树算法");
  201. }
  202. // 获取Ai下棋地址
  203. Ai ai = Ai.getInstance();
  204. ChessPainter._position = ai.searchPosition();
  205. // 设置棋子外观
  206. ChessPainter._position!.chess.chessShape = CircleShape();
  207. // 加入决策中
  208. Ai.getInstance().addChessman(ChessPainter._position!.dx.toInt(),
  209. ChessPainter._position!.dy.toInt(), -1);
  210. if (ChessPainter._position!.chess is BlackChess &&
  211. Ai.getInstance().isWin(ChessPainter._position!.dx.toInt(),
  212. ChessPainter._position!.dy.toInt(), -1)) {
  213. TipsDialog.show(context, "很遗憾", "决策树算法打败了您");
  214. }
  215. setState(() {
  216. ChessPainter._position!.dx = ChessPainter._position!.dx * (width / 15);
  217. ChessPainter._position!.dy = ChessPainter._position!.dy * (width / 15);
  218. });
  219. }
  220. }
  221. class ChessPainter extends CustomPainter {
  222. static Position? _position;
  223. final Function _function;
  224. Checkerboard _originator = Checkerboard.getInstance();
  225. ChessPainter(Function f) : _function = f;
  226. @override
  227. void paint(Canvas canvas, Size size) {
  228. if (_position == null) {
  229. return;
  230. }
  231. bool add = false;
  232. double mWidth = size.width / 15; // 每行/列 15 个
  233. double mHeight = size.height / 15;
  234. var mPaint = Paint();
  235. //求两个点之间的距离,让棋子正确的显示在坐标轴上面
  236. var dx = _position!.dx;
  237. var dy = _position!.dy;
  238. for (int i = 0; i < CheckerBoardPainter._crossOverBeanList.length; i++) {
  239. var absX =
  240. (dx - CheckerBoardPainter._crossOverBeanList[i]._dx).abs(); //两个点的x轴距离
  241. var absY =
  242. (dy - CheckerBoardPainter._crossOverBeanList[i]._dy).abs(); //两个点的y轴距离
  243. var s = sqrt(absX * absX +
  244. absY * absY); //利用直角三角形求斜边公式(a的平方 + b的平方 = c的平方)来计算出两点间的距离
  245. if (s <= mWidth / 2 - 2) {
  246. // 触摸点到棋盘坐标坐标点距离小于等于棋子半径,那么
  247. //找到离触摸点最近的棋盘坐标点并记录保存下来
  248. _position!.dx = CheckerBoardPainter._crossOverBeanList[i]._dx;
  249. _position!.dy = CheckerBoardPainter._crossOverBeanList[i]._dy;
  250. _originator.add(_position!);
  251. add = true;
  252. if (_position!.chess is WhiteChess) {
  253. Ai.getInstance().addChessman(
  254. _position!.dx ~/ (width / 15), _position!.dy ~/ (width / 15), 1);
  255. }
  256. // flag = false; //白子下完了,该黑子下了
  257. break;
  258. }
  259. }
  260. //画子
  261. mPaint..style = PaintingStyle.fill;
  262. if (_originator.state.isNotEmpty) {
  263. for (int i = 0; i < _originator.state.length; i++) {
  264. mPaint..color = _originator.state[i].chess.color;
  265. if (_originator.state[i].chess.chessShape.shape == 1) {
  266. canvas.drawCircle(
  267. Offset(_originator.state[i].dx, _originator.state[i].dy),
  268. min(mWidth / 2, mHeight / 2) - 2,
  269. mPaint);
  270. }
  271. if (_originator.state[i].chess.chessShape.shape == 2) {
  272. Rect rect = Rect.fromCircle(
  273. center: Offset(_originator.state[i].dx, _originator.state[i].dy),
  274. radius: min(mWidth / 2, mHeight / 2) - 2);
  275. canvas.drawRect(rect, mPaint);
  276. }
  277. }
  278. }
  279. WidgetsBinding.instance.addPostFrameCallback((_) {
  280. if (add && _position!.chess is WhiteChess) {
  281. _function();
  282. }
  283. });
  284. }
  285. //在实际场景中正确利用此回调可以避免重绘开销,本示例我们简单的返回true
  286. @override
  287. bool shouldRepaint(CustomPainter oldDelegate) {
  288. return true;
  289. }
  290. }
  291. class CheckerBoardPainter extends CustomPainter {
  292. static List<CrossOverBean> _crossOverBeanList = [];
  293. @override
  294. void paint(Canvas canvas, Size size) {
  295. double mWidth = size.width / 15;
  296. double mHeight = size.height / 15;
  297. var mPaint = Paint();
  298. _crossOverBeanList.clear();
  299. //重绘下整个界面的画布北京颜色
  300. //设置画笔,画棋盘背景
  301. mPaint
  302. ..isAntiAlias = true //抗锯齿
  303. ..style = PaintingStyle.fill //填充
  304. ..color = Color(0x77cdb175); //背景为纸黄色
  305. canvas.drawRect(
  306. Rect.fromCenter(
  307. center: Offset(size.width / 2, size.height / 2),
  308. width: size.width,
  309. height: size.height),
  310. mPaint);
  311. //画棋盘网格
  312. mPaint
  313. ..style = PaintingStyle.stroke
  314. ..color = CupertinoColors.systemGrey6
  315. ..strokeWidth = 1.0;
  316. for (var i = 0; i <= 15; i++) {
  317. //画横线
  318. canvas.drawLine(
  319. Offset(0, mHeight * i), Offset(size.width, mHeight * i), mPaint);
  320. }
  321. for (var i = 0; i <= 15; i++) {
  322. //画竖线
  323. canvas.drawLine(
  324. Offset(mWidth * i, 0), Offset(mWidth * i, size.height), mPaint);
  325. }
  326. //记录横竖线所有的交叉点
  327. for (int i = 0; i <= 15; i++) {
  328. for (int j = 0; j <= 15; j++) {
  329. _crossOverBeanList.add(CrossOverBean(mWidth * j, mHeight * i));
  330. }
  331. }
  332. }
  333. //在实际场景中正确利用此回调可以避免重绘开销,本示例我们简单的返回true
  334. @override
  335. bool shouldRepaint(CustomPainter oldDelegate) {
  336. return false;
  337. }
  338. }
  339. ///记录棋盘上横竖线的交叉点
  340. class CrossOverBean {
  341. double _dx;
  342. double _dy;
  343. CrossOverBean(this._dx, this._dy);
  344. }