import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:awale/models/game/game.dart'; import 'package:awale/models/settings/settings_game.dart'; import 'package:awale/models/settings/settings_global.dart'; import 'package:awale/robot/robot_player.dart'; import 'package:awale/utils/tools.dart'; part 'game_state.dart'; class GameCubit extends HydratedCubit<GameState> { GameCubit() : super(GameState( currentGame: Game.createNull(), )); 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, // Base data board: state.currentGame.board, // Game data currentPlayer: state.currentGame.currentPlayer, scores: state.currentGame.scores, currentHand: state.currentGame.currentHand, ); // 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(); robotPlay(); } 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 toggleCurrentPlayer() { state.currentGame.currentPlayer = 1 - state.currentGame.currentPlayer; refresh(); robotPlay(); } void robotPlay() async { if (!state.currentGame.isFinished && !state.currentGame.isCurrentPlayerHuman()) { final int pickedCell = RobotPlayer.pickCell(state.currentGame); await Future.delayed(const Duration(milliseconds: 500)); tapOnCell(pickedCell); } } void tapOnCell(int cellIndex) async { printlog('tapOnCell: $cellIndex'); if (!state.currentGame.isCurrentPlayerHouse(cellIndex)) { printlog('not allowed'); return; } if (state.currentGame.board.cells[cellIndex] == 0) { printlog('empty cell'); return; } if (!state.currentGame.isMoveAllowed(cellIndex)) { printlog('not allowed (need to give at least one seed to other player)'); return; } state.currentGame.animationInProgress = true; refresh(); final int lastCellIndex = await animateSeedsDistribution(cellIndex); await animateSeedsEarning(lastCellIndex); toggleCurrentPlayer(); if (!state.currentGame.canPlay()) { printlog('user has no more move to play'); state.currentGame.isFinished = true; refresh(); } state.currentGame.animationInProgress = false; refresh(); } Future<int> animateSeedsDistribution(int sourceCellIndex) async { printlog('animateSeedsDistribution / sourceCellIndex: $sourceCellIndex'); final int seedsCount = state.currentGame.board.cells[sourceCellIndex]; // empty source cell printlog('animateSeedsDistribution / empty source cell'); state.currentGame.board.cells[sourceCellIndex] = 0; state.currentGame.currentHand = seedsCount; refresh(); await Future.delayed(const Duration(milliseconds: 200)); int cellIndex = sourceCellIndex; for (int i = 0; i < seedsCount; i++) { cellIndex = state.currentGame.getNextCellIndex(cellIndex, sourceCellIndex); state.currentGame.currentHand--; state.currentGame.board.cells[cellIndex] += 1; refresh(); await Future.delayed(const Duration(milliseconds: 300)); } refresh(); return cellIndex; } Future<int> animateSeedsEarning(int lastCellIndex) async { printlog('animateSeedsEarning / lastCellIndex: $lastCellIndex'); int earnedSeedsCount = 0; if (state.currentGame.isOpponentHouse(lastCellIndex)) { final int seedsCount = state.currentGame.board.cells[lastCellIndex]; printlog('found $seedsCount seed(s) on final house'); if ([2, 3].contains(seedsCount)) { printlog('-> ok will earn these seeds'); state.currentGame.board.cells[lastCellIndex] = 0; state.currentGame.scores[state.currentGame.currentPlayer] += seedsCount; earnedSeedsCount += seedsCount; refresh(); await Future.delayed(const Duration(milliseconds: 500)); // (recursively) check previous cells printlog('-> dispatch to previous cell'); final int previousCellIndex = state.currentGame.getPreviousCellIndex(lastCellIndex); earnedSeedsCount += await animateSeedsEarning(previousCellIndex); } else { printlog('-> nothing to do'); } } return earnedSeedsCount; } @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(), }; } }