home_page.dart 13 KB

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