home_page.dart 13 KB

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