import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:my_flutter_puzzle/res/palette.dart'; import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; import 'package:rive/rive.dart'; class SoloLargeScreen extends StatefulWidget { const SoloLargeScreen({ Key? key, }) : super(key: key); @override _SoloLargeScreenState createState() => _SoloLargeScreenState(); } class _SoloLargeScreenState extends State { late PuzzleSolverClient _solverClient; late RiveAnimationController _riveController; List>? _board2D; List? myList; int _moves = 0; final _puzzleSize = 3; final int _animationSpeedInMilliseconds = 300; bool _isComputing = false; bool _isAutoSolving = false; bool _isSolved = false; Map? _offsetMap; List _solvedList = []; @override void initState() { super.initState(); _solverClient = PuzzleSolverClient(size: _puzzleSize); _riveController = SimpleAnimation('idle'); initBoard(); generateSolvedList(); } generateSolvedList() { for (int i = 1; i < _puzzleSize * _puzzleSize; i++) { _solvedList.add(i); } _solvedList.add(0); } scrambleBoard() { final generated2DBoard = _solverClient.createRandomBoard(); final generated1DBoard = _solverClient.convertTo1D(generated2DBoard); updateOffset(generated1DBoard); setState(() { _board2D = generated2DBoard; myList = generated1DBoard; _moves = 0; _isSolved = false; }); } initBoard() { final generated2DBoard = _solverClient.createRandomBoard(); final generated1DBoard = _solverClient.convertTo1D(generated2DBoard); createOffset(generated1DBoard); setState(() { _board2D = generated2DBoard; myList = generated1DBoard; _moves = 0; }); } startAutoSolver() async { if (_board2D != null) { setState(() { _isComputing = true; }); List>? boardStates = await compute( _solverClient.runner, _solverClient.convertTo2D(myList!)); setState(() { _isComputing = false; _isAutoSolving = true; }); if (boardStates != null) { for (var board in boardStates) { await Future.delayed(Duration( milliseconds: _animationSpeedInMilliseconds, )); setState(() { myList = board; _moves++; }); updateOffset(myList!); } } } setState(() { _isAutoSolving = false; _isSolved = true; }); showCompleteDialogBox(context); } isSolved(List currentBoard) { if (listEquals(currentBoard, _solvedList)) { setState(() { _isSolved = true; }); return true; } setState(() { _isSolved = false; }); return false; } onClick(index) { log('-----------------------'); log('Tapped index: $index'); if (myList != null) { int emptyTilePosIndex = myList!.indexOf(0); int emptyTilePosRow = emptyTilePosIndex ~/ _puzzleSize; int emptyTilePosCol = emptyTilePosIndex % _puzzleSize; int currentTileRow = index ~/ _puzzleSize; int currentTileCol = index % _puzzleSize; //current element moves up if ((currentTileRow - 1 == emptyTilePosRow) && (currentTileCol == emptyTilePosCol)) { myList![emptyTilePosIndex] = myList![index]; myList![index] = 0; _moves++; } //current element moves down else if ((currentTileRow + 1 == emptyTilePosRow) && (currentTileCol == emptyTilePosCol)) { myList![emptyTilePosIndex] = myList![index]; myList![index] = 0; _moves++; } //current element moves left else if ((currentTileRow == emptyTilePosRow) && (currentTileCol + 1 == emptyTilePosCol)) { myList![emptyTilePosIndex] = myList![index]; myList![index] = 0; _moves++; } //current element moves right else if ((currentTileRow == emptyTilePosRow) && (currentTileCol - 1 == emptyTilePosCol)) { myList![emptyTilePosIndex] = myList![index]; myList![index] = 0; _moves++; } else { if (currentTileCol == emptyTilePosCol) { int low; int high; // multiple elements move up if (emptyTilePosRow < currentTileRow) { low = emptyTilePosRow; high = currentTileRow; int i = low; while (i < high) { myList![(i * _puzzleSize) + emptyTilePosCol] = myList![(((i + 1) * _puzzleSize) + emptyTilePosCol)]; i += 1; } myList![(high * _puzzleSize) + emptyTilePosCol] = 0; _moves++; } //multiple elements move down else { low = emptyTilePosRow; high = currentTileRow; int i = low; while (i > high) { myList![(i * _puzzleSize) + emptyTilePosCol] = myList![(((i - 1) * _puzzleSize) + emptyTilePosCol)]; i -= 1; } myList![(high * _puzzleSize) + emptyTilePosCol] = 0; _moves++; } } // multiple elements move left or right if (currentTileRow == emptyTilePosRow) { int low; int high; // multiple elements move left if (emptyTilePosCol < currentTileCol) { low = emptyTilePosCol; high = currentTileCol; int i = low; while (i < high) { myList![(emptyTilePosRow * _puzzleSize) + i] = myList![(emptyTilePosRow * _puzzleSize) + (i + 1)]; i += 1; } myList![high + (emptyTilePosRow * _puzzleSize)] = 0; _moves++; } //multiple elements move right else { low = emptyTilePosCol; high = currentTileCol; int i = low; while (i > high) { myList![(i + (emptyTilePosRow * _puzzleSize))] = myList![(i - 1) + (emptyTilePosRow * _puzzleSize)]; i -= 1; } myList![high + (emptyTilePosRow * _puzzleSize)] = 0; _moves++; } } } // Update Offset list // setState(() { // updateOffset(myList!); // }); updateOffset(myList!); setState(() {}); if (isSolved(myList!)) { showCompleteDialogBox(context); } log('List: $myList'); log('-----------------------'); } } createOffset(List board) { Map offsetMap = {}; int j = 0; log('BOARD: $board'); for (int i = 0; i < board.length; i++) { final xMod = i % _puzzleSize; double x = xMod / (_puzzleSize - 1); if (x % i == 0 && i != 0) j++; int yMod = j % _puzzleSize; double y = yMod / (_puzzleSize - 1); offsetMap.addEntries([ MapEntry( board[i], FractionalOffset(x, y), ) ]); } log('INITIAL OFFSET MAP: $offsetMap'); setState(() { _offsetMap = offsetMap; }); } updateOffset(List board) { int j = 0; for (int i = 0; i < board.length; i++) { final xMod = i % _puzzleSize; double x = xMod / (_puzzleSize - 1); if (x % i == 0 && i != 0) j++; int yMod = j % _puzzleSize; double y = yMod / (_puzzleSize - 1); _offsetMap![board[i]] = FractionalOffset(x, y); } log('OFFSET MAP: $_offsetMap'); } showCompleteDialogBox(BuildContext context) { showDialog( context: context, builder: (context) => Dialog( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Solved successfully!', style: TextStyle(fontSize: 22), ), const SizedBox(height: 16), ElevatedButton( style: ElevatedButton.styleFrom( primary: Palette.violet, onSurface: Palette.violet, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), ), onPressed: () { Navigator.of(context).pop(); }, child: const Padding( padding: EdgeInsets.all(16.0), child: Text( 'OK', style: TextStyle(fontSize: 22), ), ), ), ], ), ), ), ); } @override Widget build(BuildContext context) { var screenSize = MediaQuery.of(context).size; var shortestSide = screenSize.shortestSide; var fontSize = shortestSide * 0.08; var boardSize = shortestSide * 0.45; var spacing = 5; var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1)); return Scaffold( backgroundColor: Colors.black, appBar: PreferredSize( child: Container( color: Colors.black, ), preferredSize: Size(double.maxFinite, shortestSide * 0.1), ), body: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.only(left: 56.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(), const Text( 'Photo', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, color: Colors.white, ), ), const SizedBox(height: 8), const Text( 'Puzzle', style: TextStyle( fontSize: 58, fontWeight: FontWeight.w500, color: Colors.white, ), ), const Text( 'Challenge', style: TextStyle( fontSize: 58, fontWeight: FontWeight.w500, color: Colors.white, ), ), const SizedBox(height: 32), RichText( text: TextSpan( style: const TextStyle( fontSize: 24, // fontWeight: FontWeight.w500, color: Colors.white, ), children: [ TextSpan( text: _moves.toString(), style: const TextStyle( fontWeight: FontWeight.w600, ), ), const TextSpan(text: ' Moves | '), const TextSpan( text: '15', style: TextStyle( fontWeight: FontWeight.w600, ), ), const TextSpan(text: ' Tiles'), ], ), ), const SizedBox(height: 32), SizedBox( width: 145, child: ElevatedButton( style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), ), onPressed: () {}, child: const Padding( padding: EdgeInsets.only(top: 13.0, bottom: 12.0), child: Text( 'Start Game', // 'Restart', // 'Get ready...', style: TextStyle( fontSize: 16, ), ), ), ), ), ], ), ), myList != null && _offsetMap != null ? Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // Row(), // MovesText( // moves: _moves, // fontSize: fontSize, // ), Row( mainAxisSize: MainAxisSize.min, children: const [ Text( '00:00:00', style: TextStyle( fontSize: 40, fontWeight: FontWeight.bold, color: Colors.white, ), ), SizedBox(width: 8), Icon( Icons.timer, color: Colors.white, size: 40, ) ], ), SizedBox( height: boardSize, width: boardSize, child: Stack( children: [ for (int i = 0; i < _offsetMap!.length; i++) _offsetMap!.entries.toList()[i].key != 0 ? AnimatedAlign( alignment: _offsetMap!.entries.toList()[i].value, duration: Duration( milliseconds: _animationSpeedInMilliseconds, ), curve: Curves.easeInOut, child: GestureDetector( onTap: () => onClick(myList!.indexOf( _offsetMap!.entries.toList()[i].key)), child: Card( elevation: 4, color: const Color(0xFF2868d7), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: SizedBox( height: eachBoxSize, width: eachBoxSize, child: Center( child: Text( _offsetMap!.entries .toList()[i] .key .toString(), style: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ), ), ) : const SizedBox(), ], ), ), const SizedBox(height: 30), // Row( // mainAxisSize: MainAxisSize.min, // children: [ // Padding( // padding: const EdgeInsets.only(bottom: 30.0), // child: SizedBox( // width: MediaQuery.of(context).size.width * 0.2, // child: ElevatedButton( // style: ElevatedButton.styleFrom( // primary: Palette.violet, // onSurface: Palette.violet, // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(50), // ), // ), // onPressed: _isComputing || _isAutoSolving || _isSolved // ? null // : () { // startAutoSolver(); // }, // child: Padding( // padding: const EdgeInsets.all(16.0), // child: _isComputing || _isAutoSolving // ? Row( // children: [ // const SizedBox( // width: 25, // height: 25, // child: CircularProgressIndicator( // color: Palette.violet, // strokeWidth: 2, // ), // ), // const SizedBox(width: 16), // Text( // _isComputing // ? 'Computing ...' // : 'Solving ...', // style: const TextStyle(fontSize: 20), // ), // ], // ) // : const Text( // 'Start Auto Solver', // style: TextStyle(fontSize: 22), // ), // ), // ), // ), // ), // const SizedBox(width: 16.0), // Padding( // padding: const EdgeInsets.only(bottom: 30.0), // child: SizedBox( // width: MediaQuery.of(context).size.width * 0.2, // child: ElevatedButton( // style: ElevatedButton.styleFrom( // primary: Palette.crimson, // onSurface: Palette.crimson, // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(50), // ), // ), // onPressed: _isComputing || _isAutoSolving // ? null // : () { // scrambleBoard(); // }, // child: const Padding( // padding: EdgeInsets.all(16.0), // child: Text( // 'Scramble', // style: TextStyle(fontSize: 22), // ), // ), // ), // ), // ), // ], // ), ], ) : const SizedBox(), Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.only(right: 56.0, bottom: 56), child: SizedBox( width: boardSize * 0.75, height: boardSize * 0.75, child: RiveAnimation.asset( 'assets/rive/dash.riv', fit: BoxFit.contain, antialiasing: true, controllers: [_riveController], onInit: (_) => setState(() {}), ), ), ), ), // SizedBox(), ], ), ); } }