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.

635 lines
21 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:rive/rive.dart';
  7. class SoloLargeScreen extends StatefulWidget {
  8. const SoloLargeScreen({
  9. Key? key,
  10. }) : super(key: key);
  11. @override
  12. _SoloLargeScreenState createState() => _SoloLargeScreenState();
  13. }
  14. class _SoloLargeScreenState extends State<SoloLargeScreen> {
  15. late PuzzleSolverClient _solverClient;
  16. late RiveAnimationController _riveController;
  17. List<List<int>>? _board2D;
  18. List<int>? myList;
  19. int _moves = 0;
  20. final _puzzleSize = 3;
  21. final int _animationSpeedInMilliseconds = 300;
  22. bool _isComputing = false;
  23. bool _isAutoSolving = false;
  24. bool _isSolved = false;
  25. Map<int, FractionalOffset>? _offsetMap;
  26. List<int> _solvedList = [];
  27. @override
  28. void initState() {
  29. super.initState();
  30. _solverClient = PuzzleSolverClient(size: _puzzleSize);
  31. _riveController = SimpleAnimation('idle');
  32. initBoard();
  33. generateSolvedList();
  34. }
  35. generateSolvedList() {
  36. for (int i = 1; i < _puzzleSize * _puzzleSize; i++) {
  37. _solvedList.add(i);
  38. }
  39. _solvedList.add(0);
  40. }
  41. scrambleBoard() {
  42. final generated2DBoard = _solverClient.createRandomBoard();
  43. final generated1DBoard = _solverClient.convertTo1D(generated2DBoard);
  44. updateOffset(generated1DBoard);
  45. setState(() {
  46. _board2D = generated2DBoard;
  47. myList = generated1DBoard;
  48. _moves = 0;
  49. _isSolved = false;
  50. });
  51. }
  52. initBoard() {
  53. final generated2DBoard = _solverClient.createRandomBoard();
  54. final generated1DBoard = _solverClient.convertTo1D(generated2DBoard);
  55. createOffset(generated1DBoard);
  56. setState(() {
  57. _board2D = generated2DBoard;
  58. myList = generated1DBoard;
  59. _moves = 0;
  60. });
  61. }
  62. startAutoSolver() async {
  63. if (_board2D != null) {
  64. setState(() {
  65. _isComputing = true;
  66. });
  67. List<List<int>>? boardStates = await compute(
  68. _solverClient.runner, _solverClient.convertTo2D(myList!));
  69. setState(() {
  70. _isComputing = false;
  71. _isAutoSolving = true;
  72. });
  73. if (boardStates != null) {
  74. for (var board in boardStates) {
  75. await Future.delayed(Duration(
  76. milliseconds: _animationSpeedInMilliseconds,
  77. ));
  78. setState(() {
  79. myList = board;
  80. _moves++;
  81. });
  82. updateOffset(myList!);
  83. }
  84. }
  85. }
  86. setState(() {
  87. _isAutoSolving = false;
  88. _isSolved = true;
  89. });
  90. showCompleteDialogBox(context);
  91. }
  92. isSolved(List<int> currentBoard) {
  93. if (listEquals(currentBoard, _solvedList)) {
  94. setState(() {
  95. _isSolved = true;
  96. });
  97. return true;
  98. }
  99. setState(() {
  100. _isSolved = false;
  101. });
  102. return false;
  103. }
  104. onClick(index) {
  105. log('-----------------------');
  106. log('Tapped index: $index');
  107. if (myList != null) {
  108. int emptyTilePosIndex = myList!.indexOf(0);
  109. int emptyTilePosRow = emptyTilePosIndex ~/ _puzzleSize;
  110. int emptyTilePosCol = emptyTilePosIndex % _puzzleSize;
  111. int currentTileRow = index ~/ _puzzleSize;
  112. int currentTileCol = index % _puzzleSize;
  113. //current element moves up
  114. if ((currentTileRow - 1 == emptyTilePosRow) &&
  115. (currentTileCol == emptyTilePosCol)) {
  116. myList![emptyTilePosIndex] = myList![index];
  117. myList![index] = 0;
  118. _moves++;
  119. }
  120. //current element moves down
  121. else if ((currentTileRow + 1 == emptyTilePosRow) &&
  122. (currentTileCol == emptyTilePosCol)) {
  123. myList![emptyTilePosIndex] = myList![index];
  124. myList![index] = 0;
  125. _moves++;
  126. }
  127. //current element moves left
  128. else if ((currentTileRow == emptyTilePosRow) &&
  129. (currentTileCol + 1 == emptyTilePosCol)) {
  130. myList![emptyTilePosIndex] = myList![index];
  131. myList![index] = 0;
  132. _moves++;
  133. }
  134. //current element moves right
  135. else if ((currentTileRow == emptyTilePosRow) &&
  136. (currentTileCol - 1 == emptyTilePosCol)) {
  137. myList![emptyTilePosIndex] = myList![index];
  138. myList![index] = 0;
  139. _moves++;
  140. } else {
  141. if (currentTileCol == emptyTilePosCol) {
  142. int low;
  143. int high;
  144. // multiple elements move up
  145. if (emptyTilePosRow < currentTileRow) {
  146. low = emptyTilePosRow;
  147. high = currentTileRow;
  148. int i = low;
  149. while (i < high) {
  150. myList![(i * _puzzleSize) + emptyTilePosCol] =
  151. myList![(((i + 1) * _puzzleSize) + emptyTilePosCol)];
  152. i += 1;
  153. }
  154. myList![(high * _puzzleSize) + emptyTilePosCol] = 0;
  155. _moves++;
  156. }
  157. //multiple elements move down
  158. else {
  159. low = emptyTilePosRow;
  160. high = currentTileRow;
  161. int i = low;
  162. while (i > high) {
  163. myList![(i * _puzzleSize) + emptyTilePosCol] =
  164. myList![(((i - 1) * _puzzleSize) + emptyTilePosCol)];
  165. i -= 1;
  166. }
  167. myList![(high * _puzzleSize) + emptyTilePosCol] = 0;
  168. _moves++;
  169. }
  170. }
  171. // multiple elements move left or right
  172. if (currentTileRow == emptyTilePosRow) {
  173. int low;
  174. int high;
  175. // multiple elements move left
  176. if (emptyTilePosCol < currentTileCol) {
  177. low = emptyTilePosCol;
  178. high = currentTileCol;
  179. int i = low;
  180. while (i < high) {
  181. myList![(emptyTilePosRow * _puzzleSize) + i] =
  182. myList![(emptyTilePosRow * _puzzleSize) + (i + 1)];
  183. i += 1;
  184. }
  185. myList![high + (emptyTilePosRow * _puzzleSize)] = 0;
  186. _moves++;
  187. }
  188. //multiple elements move right
  189. else {
  190. low = emptyTilePosCol;
  191. high = currentTileCol;
  192. int i = low;
  193. while (i > high) {
  194. myList![(i + (emptyTilePosRow * _puzzleSize))] =
  195. myList![(i - 1) + (emptyTilePosRow * _puzzleSize)];
  196. i -= 1;
  197. }
  198. myList![high + (emptyTilePosRow * _puzzleSize)] = 0;
  199. _moves++;
  200. }
  201. }
  202. }
  203. // Update Offset list
  204. // setState(() {
  205. // updateOffset(myList!);
  206. // });
  207. updateOffset(myList!);
  208. setState(() {});
  209. if (isSolved(myList!)) {
  210. showCompleteDialogBox(context);
  211. }
  212. log('List: $myList');
  213. log('-----------------------');
  214. }
  215. }
  216. createOffset(List<int> board) {
  217. Map<int, FractionalOffset> offsetMap = {};
  218. int j = 0;
  219. log('BOARD: $board');
  220. for (int i = 0; i < board.length; i++) {
  221. final xMod = i % _puzzleSize;
  222. double x = xMod / (_puzzleSize - 1);
  223. if (x % i == 0 && i != 0) j++;
  224. int yMod = j % _puzzleSize;
  225. double y = yMod / (_puzzleSize - 1);
  226. offsetMap.addEntries([
  227. MapEntry<int, FractionalOffset>(
  228. board[i],
  229. FractionalOffset(x, y),
  230. )
  231. ]);
  232. }
  233. log('INITIAL OFFSET MAP: $offsetMap');
  234. setState(() {
  235. _offsetMap = offsetMap;
  236. });
  237. }
  238. updateOffset(List<int> board) {
  239. int j = 0;
  240. for (int i = 0; i < board.length; i++) {
  241. final xMod = i % _puzzleSize;
  242. double x = xMod / (_puzzleSize - 1);
  243. if (x % i == 0 && i != 0) j++;
  244. int yMod = j % _puzzleSize;
  245. double y = yMod / (_puzzleSize - 1);
  246. _offsetMap![board[i]] = FractionalOffset(x, y);
  247. }
  248. log('OFFSET MAP: $_offsetMap');
  249. }
  250. showCompleteDialogBox(BuildContext context) {
  251. showDialog(
  252. context: context,
  253. builder: (context) => Dialog(
  254. child: Padding(
  255. padding: const EdgeInsets.all(24.0),
  256. child: Column(
  257. mainAxisSize: MainAxisSize.min,
  258. children: [
  259. const Text(
  260. 'Solved successfully!',
  261. style: TextStyle(fontSize: 22),
  262. ),
  263. const SizedBox(height: 16),
  264. ElevatedButton(
  265. style: ElevatedButton.styleFrom(
  266. primary: Palette.violet,
  267. onSurface: Palette.violet,
  268. shape: RoundedRectangleBorder(
  269. borderRadius: BorderRadius.circular(50),
  270. ),
  271. ),
  272. onPressed: () {
  273. Navigator.of(context).pop();
  274. },
  275. child: const Padding(
  276. padding: EdgeInsets.all(16.0),
  277. child: Text(
  278. 'OK',
  279. style: TextStyle(fontSize: 22),
  280. ),
  281. ),
  282. ),
  283. ],
  284. ),
  285. ),
  286. ),
  287. );
  288. }
  289. @override
  290. Widget build(BuildContext context) {
  291. var screenSize = MediaQuery.of(context).size;
  292. var shortestSide = screenSize.shortestSide;
  293. var fontSize = shortestSide * 0.08;
  294. var boardSize = shortestSide * 0.45;
  295. var spacing = 5;
  296. var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1));
  297. return Scaffold(
  298. backgroundColor: Colors.black,
  299. appBar: PreferredSize(
  300. child: Container(
  301. color: Colors.black,
  302. ),
  303. preferredSize: Size(double.maxFinite, shortestSide * 0.1),
  304. ),
  305. body: Row(
  306. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  307. children: [
  308. Padding(
  309. padding: const EdgeInsets.only(left: 56.0),
  310. child: Column(
  311. mainAxisAlignment: MainAxisAlignment.center,
  312. crossAxisAlignment: CrossAxisAlignment.start,
  313. children: [
  314. Row(),
  315. const Text(
  316. 'Photo',
  317. style: TextStyle(
  318. fontSize: 18,
  319. fontWeight: FontWeight.w500,
  320. color: Colors.white,
  321. ),
  322. ),
  323. const SizedBox(height: 8),
  324. const Text(
  325. 'Puzzle',
  326. style: TextStyle(
  327. fontSize: 58,
  328. fontWeight: FontWeight.w500,
  329. color: Colors.white,
  330. ),
  331. ),
  332. const Text(
  333. 'Challenge',
  334. style: TextStyle(
  335. fontSize: 58,
  336. fontWeight: FontWeight.w500,
  337. color: Colors.white,
  338. ),
  339. ),
  340. const SizedBox(height: 32),
  341. RichText(
  342. text: TextSpan(
  343. style: const TextStyle(
  344. fontSize: 24,
  345. // fontWeight: FontWeight.w500,
  346. color: Colors.white,
  347. ),
  348. children: [
  349. TextSpan(
  350. text: _moves.toString(),
  351. style: const TextStyle(
  352. fontWeight: FontWeight.w600,
  353. ),
  354. ),
  355. const TextSpan(text: ' Moves | '),
  356. const TextSpan(
  357. text: '15',
  358. style: TextStyle(
  359. fontWeight: FontWeight.w600,
  360. ),
  361. ),
  362. const TextSpan(text: ' Tiles'),
  363. ],
  364. ),
  365. ),
  366. const SizedBox(height: 32),
  367. SizedBox(
  368. width: 145,
  369. child: ElevatedButton(
  370. style: ElevatedButton.styleFrom(
  371. shape: RoundedRectangleBorder(
  372. borderRadius: BorderRadius.circular(30),
  373. ),
  374. ),
  375. onPressed: () {},
  376. child: const Padding(
  377. padding: EdgeInsets.only(top: 13.0, bottom: 12.0),
  378. child: Text(
  379. 'Start Game',
  380. // 'Restart',
  381. // 'Get ready...',
  382. style: TextStyle(
  383. fontSize: 16,
  384. ),
  385. ),
  386. ),
  387. ),
  388. ),
  389. ],
  390. ),
  391. ),
  392. myList != null && _offsetMap != null
  393. ? Column(
  394. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  395. children: [
  396. // Row(),
  397. // MovesText(
  398. // moves: _moves,
  399. // fontSize: fontSize,
  400. // ),
  401. Row(
  402. mainAxisSize: MainAxisSize.min,
  403. children: const [
  404. Text(
  405. '00:00:00',
  406. style: TextStyle(
  407. fontSize: 40,
  408. fontWeight: FontWeight.bold,
  409. color: Colors.white,
  410. ),
  411. ),
  412. SizedBox(width: 8),
  413. Icon(
  414. Icons.timer,
  415. color: Colors.white,
  416. size: 40,
  417. )
  418. ],
  419. ),
  420. SizedBox(
  421. height: boardSize,
  422. width: boardSize,
  423. child: Stack(
  424. children: [
  425. for (int i = 0; i < _offsetMap!.length; i++)
  426. _offsetMap!.entries.toList()[i].key != 0
  427. ? AnimatedAlign(
  428. alignment:
  429. _offsetMap!.entries.toList()[i].value,
  430. duration: Duration(
  431. milliseconds:
  432. _animationSpeedInMilliseconds,
  433. ),
  434. curve: Curves.easeInOut,
  435. child: GestureDetector(
  436. onTap: () => onClick(myList!.indexOf(
  437. _offsetMap!.entries.toList()[i].key)),
  438. child: Card(
  439. elevation: 4,
  440. color: const Color(0xFF2868d7),
  441. shape: RoundedRectangleBorder(
  442. borderRadius:
  443. BorderRadius.circular(20),
  444. ),
  445. child: SizedBox(
  446. height: eachBoxSize,
  447. width: eachBoxSize,
  448. child: Center(
  449. child: Text(
  450. _offsetMap!.entries
  451. .toList()[i]
  452. .key
  453. .toString(),
  454. style: TextStyle(
  455. fontSize: fontSize,
  456. fontWeight: FontWeight.bold,
  457. color: Colors.white,
  458. ),
  459. ),
  460. ),
  461. ),
  462. ),
  463. ),
  464. )
  465. : const SizedBox(),
  466. ],
  467. ),
  468. ),
  469. const SizedBox(height: 30),
  470. // Row(
  471. // mainAxisSize: MainAxisSize.min,
  472. // children: [
  473. // Padding(
  474. // padding: const EdgeInsets.only(bottom: 30.0),
  475. // child: SizedBox(
  476. // width: MediaQuery.of(context).size.width * 0.2,
  477. // child: ElevatedButton(
  478. // style: ElevatedButton.styleFrom(
  479. // primary: Palette.violet,
  480. // onSurface: Palette.violet,
  481. // shape: RoundedRectangleBorder(
  482. // borderRadius: BorderRadius.circular(50),
  483. // ),
  484. // ),
  485. // onPressed: _isComputing || _isAutoSolving || _isSolved
  486. // ? null
  487. // : () {
  488. // startAutoSolver();
  489. // },
  490. // child: Padding(
  491. // padding: const EdgeInsets.all(16.0),
  492. // child: _isComputing || _isAutoSolving
  493. // ? Row(
  494. // children: [
  495. // const SizedBox(
  496. // width: 25,
  497. // height: 25,
  498. // child: CircularProgressIndicator(
  499. // color: Palette.violet,
  500. // strokeWidth: 2,
  501. // ),
  502. // ),
  503. // const SizedBox(width: 16),
  504. // Text(
  505. // _isComputing
  506. // ? 'Computing ...'
  507. // : 'Solving ...',
  508. // style: const TextStyle(fontSize: 20),
  509. // ),
  510. // ],
  511. // )
  512. // : const Text(
  513. // 'Start Auto Solver',
  514. // style: TextStyle(fontSize: 22),
  515. // ),
  516. // ),
  517. // ),
  518. // ),
  519. // ),
  520. // const SizedBox(width: 16.0),
  521. // Padding(
  522. // padding: const EdgeInsets.only(bottom: 30.0),
  523. // child: SizedBox(
  524. // width: MediaQuery.of(context).size.width * 0.2,
  525. // child: ElevatedButton(
  526. // style: ElevatedButton.styleFrom(
  527. // primary: Palette.crimson,
  528. // onSurface: Palette.crimson,
  529. // shape: RoundedRectangleBorder(
  530. // borderRadius: BorderRadius.circular(50),
  531. // ),
  532. // ),
  533. // onPressed: _isComputing || _isAutoSolving
  534. // ? null
  535. // : () {
  536. // scrambleBoard();
  537. // },
  538. // child: const Padding(
  539. // padding: EdgeInsets.all(16.0),
  540. // child: Text(
  541. // 'Scramble',
  542. // style: TextStyle(fontSize: 22),
  543. // ),
  544. // ),
  545. // ),
  546. // ),
  547. // ),
  548. // ],
  549. // ),
  550. ],
  551. )
  552. : const SizedBox(),
  553. Align(
  554. alignment: Alignment.bottomCenter,
  555. child: Padding(
  556. padding: const EdgeInsets.only(right: 56.0, bottom: 56),
  557. child: SizedBox(
  558. width: boardSize * 0.75,
  559. height: boardSize * 0.75,
  560. child: RiveAnimation.asset(
  561. 'assets/rive/dash.riv',
  562. fit: BoxFit.contain,
  563. antialiasing: true,
  564. controllers: [_riveController],
  565. onInit: (_) => setState(() {}),
  566. ),
  567. ),
  568. ),
  569. ),
  570. // SizedBox(),
  571. ],
  572. ),
  573. );
  574. }
  575. }