import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; import 'package:image/image.dart' as imglib; import 'package:puzzlegame/models/game/game.dart'; import 'package:puzzlegame/models/game/moving_tile.dart'; import 'package:puzzlegame/models/settings/settings_game.dart'; import 'package:puzzlegame/models/settings/settings_global.dart'; part 'game_state.dart'; class GameCubit extends HydratedCubit<GameState> { GameCubit() : super(GameState( currentGame: Game.createEmpty(), )); void updateState(Game game) { emit(GameState( currentGame: game, )); } void refresh() { final Game game = Game( // Settings gameSettings: state.currentGame.gameSettings, globalSettings: state.currentGame.globalSettings, // State isRunning: state.currentGame.isRunning, isStarted: state.currentGame.isStarted, isFinished: state.currentGame.isFinished, animationInProgress: state.currentGame.animationInProgress, shufflingInProgress: state.currentGame.shufflingInProgress, // Base data image: state.currentGame.image, tiles: state.currentGame.tiles, // Game data movesCount: state.currentGame.movesCount, displayTip: state.currentGame.displayTip, tileSize: state.currentGame.tileSize, ); // game.dump(); updateState(game); } void startNewGame({ required GameSettings gameSettings, required GlobalSettings globalSettings, }) { final Game newGame = Game.createNew( // Settings gameSettings: gameSettings, globalSettings: globalSettings, ); newGame.dump(); updateState(newGame); refresh(); state.currentGame.isRunning = true; state.currentGame.shufflingInProgress = true; refresh(); Timer(const Duration(seconds: 1), () { splitImageInTiles(); }); } void quitGame() { state.currentGame.isRunning = false; refresh(); } void resumeSavedGame() { state.currentGame.isRunning = true; refresh(); } void deleteSavedGame() { state.currentGame.isRunning = false; state.currentGame.isFinished = true; refresh(); } void updateTileSize(double tileSize) { if (tileSize != state.currentGame.tileSize) { state.currentGame.tileSize = tileSize; for (var i = 0; i < state.currentGame.tiles.length; i++) { state.currentGame.tiles[i].size = tileSize; } refresh(); } } void toggleDisplayTipImage() { state.currentGame.displayTip = !state.currentGame.displayTip; refresh(); } void incrementMovesCount() { state.currentGame.movesCount++; refresh(); } bool checkTilesetIsCleared() { for (MovingTile tile in state.currentGame.tiles) { if (!tile.isCorrect()) { return false; } } return true; } void swapTiles(List<int> tile1, List<int> tile2) { state.currentGame.isStarted = true; final int indexTile1 = state.currentGame.tiles.indexWhere( (tile) => ((tile.currentCol == tile1[0]) && (tile.currentRow == tile1[1]))); final int indexTile2 = state.currentGame.tiles.indexWhere( (tile) => ((tile.currentCol == tile2[0]) && (tile.currentRow == tile2[1]))); final MovingTile swap = state.currentGame.tiles[indexTile1]; state.currentGame.tiles[indexTile1] = state.currentGame.tiles[indexTile2]; state.currentGame.tiles[indexTile2] = swap; final int swapCol = state.currentGame.tiles[indexTile1].currentCol; state.currentGame.tiles[indexTile1].currentCol = state.currentGame.tiles[indexTile2].currentCol; state.currentGame.tiles[indexTile2].currentCol = swapCol; final int swapRow = state.currentGame.tiles[indexTile1].currentRow; state.currentGame.tiles[indexTile1].currentRow = state.currentGame.tiles[indexTile2].currentRow; state.currentGame.tiles[indexTile2].currentRow = swapRow; incrementMovesCount(); if (checkTilesetIsCleared()) { state.currentGame.isFinished = true; } refresh(); } Future<void> splitImageInTiles() async { final String imageAsset = 'assets/images/${state.currentGame.image}.png'; final Uint8List imageData = (await rootBundle.load(imageAsset)).buffer.asUint8List(); final int tilesCount = state.currentGame.gameSettings.tilesetSizeValue; final imglib.Image image = imglib.decodeImage(imageData) ?? imglib.Image.fromBytes( height: 1, width: 1, bytes: Uint8List.fromList([]).buffer, ); int x = 0, y = 0; final int width = (image.width / tilesCount).round(); final int height = (image.height / tilesCount).round(); final List<MovingTile> tiles = []; for (int i = 0; i < tilesCount; i++) { for (int j = 0; j < tilesCount; j++) { final Uint8List tileData = Uint8List.fromList(imglib.encodeJpg(imglib.copyCrop( image, x: x, y: y, width: width, height: height, ))); tiles.add(MovingTile( currentCol: j, currentRow: i, image: Image.memory(tileData), size: state.currentGame.tileSize, originalCol: j, originalRow: i, )); x += width; } x = 0; y += height; } state.currentGame.tiles = tiles; shuffleTiles(); state.currentGame.shufflingInProgress = false; refresh(); } void shuffleTiles() { final Random random = Random(); final List<MovingTile> tiles = state.currentGame.tiles; final int tilesCount = tiles.length; for (int i = 0; i < (10 * tilesCount); i++) { final int indexTile1 = random.nextInt(tilesCount); final int indexTile2 = random.nextInt(tilesCount); final MovingTile swap = tiles[indexTile1]; tiles[indexTile1] = tiles[indexTile2]; tiles[indexTile2] = swap; final int swapCol = tiles[indexTile1].currentCol; tiles[indexTile1].currentCol = tiles[indexTile2].currentCol; tiles[indexTile2].currentCol = swapCol; final int swapRow = tiles[indexTile1].currentRow; tiles[indexTile1].currentRow = tiles[indexTile2].currentRow; tiles[indexTile2].currentRow = swapRow; } state.currentGame.tiles = tiles; } @override GameState? fromJson(Map<String, dynamic> json) { final Game currentGame = json['currentGame'] as Game; return GameState( currentGame: currentGame, ); } @override Map<String, dynamic>? toJson(GameState state) { return <String, dynamic>{ 'currentGame': state.currentGame.toJson(), }; } }