You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

489 lines
15 KiB

2 years ago
  1. import 'dart:developer';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:my_flutter_puzzle/res/palette.dart';
  5. import 'package:my_flutter_puzzle/utils/puzzle_solver.dart';
  6. import 'package:my_flutter_puzzle/widgets/puzzle_widgets/puzzle_widgets.dart';
  7. class PuzzleSoloScreen extends StatefulWidget {
  8. const PuzzleSoloScreen({
  9. Key? key,
  10. }) : super(key: key);
  11. @override
  12. _PuzzleSoloScreenState createState() => _PuzzleSoloScreenState();
  13. }
  14. class _PuzzleSoloScreenState extends State<PuzzleSoloScreen> {
  15. late final PuzzleSolverClient _solverClient;
  16. List<List<int>>? _board2D;
  17. List<int>? myList;
  18. int _moves = 0;
  19. final _puzzleSize = 3;
  20. final int _animationSpeedInMilliseconds = 300;
  21. bool _isComputing = false;
  22. bool _isAutoSolving = false;
  23. bool _isSolved = false;
  24. Map<int, FractionalOffset>? _offsetMap;
  25. List<int> _solvedList = [];
  26. @override
  27. void initState() {
  28. super.initState();
  29. _solverClient = PuzzleSolverClient(size: _puzzleSize);
  30. initBoard();
  31. generateSolvedList();
  32. }
  33. generateSolvedList() {
  34. for (int i = 1; i < _puzzleSize * _puzzleSize; i++) {
  35. _solvedList.add(i);
  36. }
  37. _solvedList.add(0);
  38. }
  39. scrambleBoard() {
  40. final generated2DBoard = _solverClient.createRandomBoard();
  41. final generated1DBoard = _solverClient.convertTo1D(generated2DBoard);
  42. updateOffset(generated1DBoard);
  43. setState(() {
  44. _board2D = generated2DBoard;
  45. myList = generated1DBoard;
  46. _moves = 0;
  47. _isSolved = false;
  48. });
  49. }
  50. initBoard() {
  51. final generated2DBoard = _solverClient.createRandomBoard();
  52. final generated1DBoard = _solverClient.convertTo1D(generated2DBoard);
  53. createOffset(generated1DBoard);
  54. setState(() {
  55. _board2D = generated2DBoard;
  56. myList = generated1DBoard;
  57. _moves = 0;
  58. });
  59. }
  60. startAutoSolver() async {
  61. if (_board2D != null) {
  62. setState(() {
  63. _isComputing = true;
  64. });
  65. List<List<int>>? boardStates = await compute(_solverClient.runner, _solverClient.convertTo2D(myList!));
  66. setState(() {
  67. _isComputing = false;
  68. _isAutoSolving = true;
  69. });
  70. if (boardStates != null) {
  71. for (var board in boardStates) {
  72. await Future.delayed(Duration(
  73. milliseconds: _animationSpeedInMilliseconds,
  74. ));
  75. setState(() {
  76. myList = board;
  77. _moves++;
  78. });
  79. updateOffset(myList!);
  80. }
  81. }
  82. }
  83. setState(() {
  84. _isAutoSolving = false;
  85. _isSolved = true;
  86. });
  87. showCompleteDialogBox(context);
  88. }
  89. isSolved(List<int> currentBoard) {
  90. if (listEquals(currentBoard, _solvedList)) {
  91. setState(() {
  92. _isSolved = true;
  93. });
  94. return true;
  95. }
  96. setState(() {
  97. _isSolved = false;
  98. });
  99. return false;
  100. }
  101. onClick(index) {
  102. log('-----------------------');
  103. log('Tapped index: $index');
  104. if (myList != null) {
  105. int emptyTilePosIndex = myList!.indexOf(0);
  106. int emptyTilePosRow = emptyTilePosIndex ~/ _puzzleSize;
  107. int emptyTilePosCol = emptyTilePosIndex % _puzzleSize;
  108. int currentTileRow = index ~/ _puzzleSize;
  109. int currentTileCol = index % _puzzleSize;
  110. //current element moves up
  111. if ((currentTileRow - 1 == emptyTilePosRow) && (currentTileCol == emptyTilePosCol)) {
  112. myList![emptyTilePosIndex] = myList![index];
  113. myList![index] = 0;
  114. _moves++;
  115. }
  116. //current element moves down
  117. else if ((currentTileRow + 1 == emptyTilePosRow) && (currentTileCol == emptyTilePosCol)) {
  118. myList![emptyTilePosIndex] = myList![index];
  119. myList![index] = 0;
  120. _moves++;
  121. }
  122. //current element moves left
  123. else if ((currentTileRow == emptyTilePosRow) && (currentTileCol + 1 == emptyTilePosCol)) {
  124. myList![emptyTilePosIndex] = myList![index];
  125. myList![index] = 0;
  126. _moves++;
  127. }
  128. //current element moves right
  129. else if ((currentTileRow == emptyTilePosRow) && (currentTileCol - 1 == emptyTilePosCol)) {
  130. myList![emptyTilePosIndex] = myList![index];
  131. myList![index] = 0;
  132. _moves++;
  133. } else {
  134. if (currentTileCol == emptyTilePosCol) {
  135. int low;
  136. int high;
  137. // multiple elements move up
  138. if (emptyTilePosRow < currentTileRow) {
  139. low = emptyTilePosRow;
  140. high = currentTileRow;
  141. int i = low;
  142. while (i < high) {
  143. myList![(i * _puzzleSize) + emptyTilePosCol] = myList![(((i + 1) * _puzzleSize) + emptyTilePosCol)];
  144. i += 1;
  145. }
  146. myList![(high * _puzzleSize) + emptyTilePosCol] = 0;
  147. _moves++;
  148. }
  149. //multiple elements move down
  150. else {
  151. low = emptyTilePosRow;
  152. high = currentTileRow;
  153. int i = low;
  154. while (i > high) {
  155. myList![(i * _puzzleSize) + emptyTilePosCol] = myList![(((i - 1) * _puzzleSize) + emptyTilePosCol)];
  156. i -= 1;
  157. }
  158. myList![(high * _puzzleSize) + emptyTilePosCol] = 0;
  159. _moves++;
  160. }
  161. }
  162. // multiple elements move left or right
  163. if (currentTileRow == emptyTilePosRow) {
  164. int low;
  165. int high;
  166. // multiple elements move left
  167. if (emptyTilePosCol < currentTileCol) {
  168. low = emptyTilePosCol;
  169. high = currentTileCol;
  170. int i = low;
  171. while (i < high) {
  172. myList![(emptyTilePosRow * _puzzleSize) + i] = myList![(emptyTilePosRow * _puzzleSize) + (i + 1)];
  173. i += 1;
  174. }
  175. myList![high + (emptyTilePosRow * _puzzleSize)] = 0;
  176. _moves++;
  177. }
  178. //multiple elements move right
  179. else {
  180. low = emptyTilePosCol;
  181. high = currentTileCol;
  182. int i = low;
  183. while (i > high) {
  184. myList![(i + (emptyTilePosRow * _puzzleSize))] = myList![(i - 1) + (emptyTilePosRow * _puzzleSize)];
  185. i -= 1;
  186. }
  187. myList![high + (emptyTilePosRow * _puzzleSize)] = 0;
  188. _moves++;
  189. }
  190. }
  191. }
  192. // Update Offset list
  193. // setState(() {
  194. // updateOffset(myList!);
  195. // });
  196. updateOffset(myList!);
  197. setState(() {});
  198. if (isSolved(myList!)) {
  199. showCompleteDialogBox(context);
  200. }
  201. log('List: $myList');
  202. log('-----------------------');
  203. }
  204. }
  205. createOffset(List<int> board) {
  206. Map<int, FractionalOffset> offsetMap = {};
  207. int j = 0;
  208. log('BOARD: $board');
  209. for (int i = 0; i < board.length; i++) {
  210. final xMod = i % _puzzleSize;
  211. double x = xMod / (_puzzleSize - 1);
  212. if (x % i == 0 && i != 0) j++;
  213. int yMod = j % _puzzleSize;
  214. double y = yMod / (_puzzleSize - 1);
  215. offsetMap.addEntries([
  216. MapEntry<int, FractionalOffset>(
  217. board[i],
  218. FractionalOffset(x, y),
  219. )
  220. ]);
  221. }
  222. log('INITIAL OFFSET MAP: $offsetMap');
  223. setState(() {
  224. _offsetMap = offsetMap;
  225. });
  226. }
  227. updateOffset(List<int> board) {
  228. int j = 0;
  229. for (int i = 0; i < board.length; i++) {
  230. final xMod = i % _puzzleSize;
  231. double x = xMod / (_puzzleSize - 1);
  232. if (x % i == 0 && i != 0) j++;
  233. int yMod = j % _puzzleSize;
  234. double y = yMod / (_puzzleSize - 1);
  235. _offsetMap![board[i]] = FractionalOffset(x, y);
  236. }
  237. log('OFFSET MAP: $_offsetMap');
  238. }
  239. showCompleteDialogBox(BuildContext context) {
  240. showDialog(
  241. context: context,
  242. builder: (context) => Dialog(
  243. child: Padding(
  244. padding: const EdgeInsets.all(24.0),
  245. child: Column(
  246. mainAxisSize: MainAxisSize.min,
  247. children: [
  248. const Text(
  249. 'Solved successfully!',
  250. style: TextStyle(fontSize: 22),
  251. ),
  252. const SizedBox(height: 16),
  253. ElevatedButton(
  254. style: ElevatedButton.styleFrom(
  255. primary: Palette.violet,
  256. onSurface: Palette.violet,
  257. shape: RoundedRectangleBorder(
  258. borderRadius: BorderRadius.circular(50),
  259. ),
  260. ),
  261. onPressed: () {
  262. Navigator.of(context).pop();
  263. },
  264. child: const Padding(
  265. padding: EdgeInsets.all(16.0),
  266. child: Text(
  267. 'OK',
  268. style: TextStyle(fontSize: 22),
  269. ),
  270. ),
  271. ),
  272. ],
  273. ),
  274. ),
  275. ),
  276. );
  277. }
  278. @override
  279. Widget build(BuildContext context) {
  280. var screenSize = MediaQuery.of(context).size;
  281. var boardSize = screenSize.height * 0.6;
  282. var spacing = 4;
  283. var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1));
  284. return Scaffold(
  285. backgroundColor: Colors.white,
  286. body: myList != null && _offsetMap != null
  287. ? Column(
  288. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  289. children: [
  290. Row(),
  291. MovesText(
  292. moves: _moves,
  293. fontSize: 60,
  294. ),
  295. SizedBox(
  296. height: boardSize,
  297. width: boardSize,
  298. child: Stack(
  299. children: [
  300. for (int i = 0; i < _offsetMap!.length; i++)
  301. _offsetMap!.entries.toList()[i].key != 0
  302. ? AnimatedAlign(
  303. alignment: _offsetMap!.entries.toList()[i].value,
  304. duration: Duration(
  305. milliseconds: _animationSpeedInMilliseconds,
  306. ),
  307. curve: Curves.easeInOut,
  308. child: GestureDetector(
  309. onTap: () => onClick(myList!.indexOf(_offsetMap!.entries.toList()[i].key)),
  310. child: Card(
  311. elevation: 4,
  312. color: const Color(0xFF2868d7),
  313. shape: RoundedRectangleBorder(
  314. borderRadius: BorderRadius.circular(20),
  315. ),
  316. child: SizedBox(
  317. height: eachBoxSize,
  318. width: eachBoxSize,
  319. child: Center(
  320. child: Text(
  321. _offsetMap!.entries.toList()[i].key.toString(),
  322. style: const TextStyle(
  323. fontSize: 60,
  324. fontWeight: FontWeight.bold,
  325. color: Colors.white,
  326. ),
  327. ),
  328. ),
  329. ),
  330. ),
  331. ),
  332. )
  333. : const SizedBox(),
  334. ],
  335. ),
  336. ),
  337. // AnimatedGrid(
  338. // puzzleSize: _puzzleSize,
  339. // key: UniqueKey(),
  340. // number: myList!,
  341. // offsetList: _offsetList!,
  342. // onTap: onClick,
  343. // color: const Color(0xFF2868d7),
  344. // ),
  345. const SizedBox(height: 30),
  346. Row(
  347. mainAxisSize: MainAxisSize.min,
  348. children: [
  349. Padding(
  350. padding: const EdgeInsets.only(bottom: 30.0),
  351. child: SizedBox(
  352. width: MediaQuery.of(context).size.width * 0.2,
  353. child: ElevatedButton(
  354. style: ElevatedButton.styleFrom(
  355. primary: Palette.violet,
  356. onSurface: Palette.violet,
  357. shape: RoundedRectangleBorder(
  358. borderRadius: BorderRadius.circular(50),
  359. ),
  360. ),
  361. onPressed: _isComputing || _isAutoSolving || _isSolved
  362. ? null
  363. : () {
  364. startAutoSolver();
  365. },
  366. child: Padding(
  367. padding: const EdgeInsets.all(16.0),
  368. child: _isComputing || _isAutoSolving
  369. ? Row(
  370. children: [
  371. const SizedBox(
  372. width: 25,
  373. height: 25,
  374. child: CircularProgressIndicator(
  375. color: Palette.violet,
  376. strokeWidth: 2,
  377. ),
  378. ),
  379. const SizedBox(width: 16),
  380. Text(
  381. _isComputing ? 'Computing ...' : 'Solving ...',
  382. style: const TextStyle(fontSize: 20),
  383. ),
  384. ],
  385. )
  386. : const Text(
  387. 'Start Auto Solver',
  388. style: TextStyle(fontSize: 22),
  389. ),
  390. ),
  391. ),
  392. ),
  393. ),
  394. const SizedBox(width: 16.0),
  395. Padding(
  396. padding: const EdgeInsets.only(bottom: 30.0),
  397. child: SizedBox(
  398. width: MediaQuery.of(context).size.width * 0.2,
  399. child: ElevatedButton(
  400. style: ElevatedButton.styleFrom(
  401. primary: Palette.crimson,
  402. onSurface: Palette.crimson,
  403. shape: RoundedRectangleBorder(
  404. borderRadius: BorderRadius.circular(50),
  405. ),
  406. ),
  407. onPressed: _isComputing || _isAutoSolving
  408. ? null
  409. : () {
  410. scrambleBoard();
  411. },
  412. child: const Padding(
  413. padding: EdgeInsets.all(16.0),
  414. child: Text(
  415. 'Scramble',
  416. style: TextStyle(fontSize: 22),
  417. ),
  418. ),
  419. ),
  420. ),
  421. ),
  422. ],
  423. ),
  424. ],
  425. )
  426. : const SizedBox(),
  427. );
  428. }
  429. }