import '../../core/config/app_constants.dart'; import '../entities/pokemon.dart'; import 'game_state.dart'; /// Règles du jeu, pures (aucune IO, aucun widget). Entièrement testable. class GameEngine { const GameEngine(); /// Nouvelle partie : réinitialise tout sauf le bestScore. GameState newGame(GameState s) { return s.copyWith( lives: AppConstants.startingLives, skips: AppConstants.startingSkips, hints: AppConstants.startingHints, sessionCorrectCount: 0, currentScore: 0, status: GameStatus.loading, isHintUsed: false, ); } /// Démarre une manche avec un nouveau Pokémon. GameState startRound(GameState s, Pokemon p, {required bool isShiny}) { return s.copyWith( currentPokemon: p, isShiny: isShiny, isHintUsed: false, status: GameStatus.playing, ); } /// Soumet une réponse. Renvoie le nouvel état + la nature du résultat. GuessOutcome submitGuess(GameState s, String guess) { final pokemon = s.currentPokemon; if (pokemon == null || s.status != GameStatus.playing) { return GuessOutcome(s, GuessResult.invalid); } final normalizedGuess = _normalize(guess.trim().toLowerCase()); final normalizedActual = _normalize(pokemon.name.toLowerCase()); if (normalizedGuess == normalizedActual) { final newSession = s.sessionCorrectCount + 1; final gained = s.isShiny ? AppConstants.pointsShiny : AppConstants.pointsNormal; final newScore = s.currentScore + gained; final hintBonus = newSession % AppConstants.hintBonusEvery == 0 ? 1 : 0; final skipBonus = newSession % AppConstants.skipBonusEvery == 0 ? 1 : 0; final newState = s.copyWith( currentPokemon: pokemon.copyWith(isCaught: true, isSeen: true), currentScore: newScore, bestScore: newScore > s.bestScore ? newScore : s.bestScore, sessionCorrectCount: newSession, hints: s.hints + hintBonus, skips: s.skips + skipBonus, status: GameStatus.roundWon, ); return GuessOutcome(newState, GuessResult.correct); } final remainingLives = s.lives - 1; if (remainingLives <= 0) { return GuessOutcome( s.copyWith(lives: 0, status: GameStatus.gameOver), GuessResult.gameOver, ); } return GuessOutcome( s.copyWith(lives: remainingLives), GuessResult.wrong, ); } /// Consomme un indice si disponible et non déjà utilisé. GameState useHint(GameState s) { if (s.isHintUsed || s.hints <= 0) return s; return s.copyWith(isHintUsed: true, hints: s.hints - 1); } /// Consomme un skip si disponible. GameState useSkip(GameState s) { if (s.skips <= 0) return s; return s.copyWith(skips: s.skips - 1); } static String _normalize(String input) { const withDia = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÖØòóôõöøÈÉÊËèéêëÇçÌÍÎÏìíîïÙÚÛÜùúûüÿÑñ'; const withoutDia = 'AAAAAAaaaaaaOOOOOOooooooEEEEeeeeCcIIIIiiiiUUUUuuuuyNn'; var output = input; for (var i = 0; i < withDia.length; i++) { output = output.replaceAll(withDia[i], withoutDia[i]); } return output; } }