// =========================================================================== // FILE: lib/logic/ai_engine.dart // =========================================================================== import 'dart:math'; import '../models/game_board.dart'; class _ClosureResult { final bool closesSomething; final int netValue; final bool causesSwap; final bool isIceTrap; _ClosureResult(this.closesSomething, this.netValue, this.causesSwap, this.isIceTrap); } class AIEngine { static Line getBestMove(GameBoard board, int level) { List availableLines = board.lines.where((l) => l.owner == Player.none && l.isPlayable).toList(); final random = Random(); if (availableLines.isEmpty) return board.lines.first; // Più il livello è alto, più l'IA è "intelligente" double smartChance = 0.50 + ((level - 1) * 0.10); if (smartChance > 1.0) smartChance = 1.0; bool beSmart = random.nextDouble() < smartChance; int myScore = board.currentPlayer == Player.red ? board.scoreRed : board.scoreBlue; int oppScore = board.currentPlayer == Player.red ? board.scoreBlue : board.scoreRed; // --- NUOVA LOGICA: GESTIONE INVERSIONE (TACTICAL FEEDING) --- // Se c'è un numero dispari di caselle Scambio aperte, il gioco è "invertito". // I punti accumulati andranno in regalo all'avversario! int swapCount = board.boxes.where((b) => b.type == BoxType.swap && !b.isClosed()).length; bool isInverted = swapCount % 2 != 0; List goodClosingMoves = []; List badClosingMoves = []; List iceTraps = []; for (var line in availableLines) { var result = _checkClosure(board, line, isInverted); if (result.isIceTrap) { iceTraps.add(line); continue; } if (result.closesSomething) { if (result.causesSwap) { if (myScore < oppScore) { goodClosingMoves.add(line); // Se perdiamo, lo scambio è la mossa vincente! } else { badClosingMoves.add(line); // Se vinciamo, NON tocchiamo lo scambio! } } else { if (result.netValue >= 0) { goodClosingMoves.add(line); } else { badClosingMoves.add(line); } } } } // --- REGOLA 1: Chiudere i quadrati vantaggiosi --- if (goodClosingMoves.isNotEmpty) { if (beSmart || random.nextDouble() < 0.70) { return goodClosingMoves[random.nextInt(goodClosingMoves.length)]; } } // --- REGOLA 2: Mosse Sicure --- List safeMoves = []; for (var line in availableLines) { if (!badClosingMoves.contains(line) && !goodClosingMoves.contains(line) && !iceTraps.contains(line) && _isSafeMove(board, line, myScore, oppScore, isInverted)) { safeMoves.add(line); } } if (safeMoves.isNotEmpty) { if (beSmart) { return safeMoves[random.nextInt(safeMoves.length)]; } else { if (random.nextDouble() < 0.5) { return safeMoves[random.nextInt(safeMoves.length)]; } } } // --- REGOLA 3: Scegliere il male minore --- if (beSmart) { List riskyButNotTerrible = availableLines.where((l) => !badClosingMoves.contains(l) && !goodClosingMoves.contains(l) && !iceTraps.contains(l)).toList(); if (riskyButNotTerrible.isNotEmpty) { return riskyButNotTerrible[random.nextInt(riskyButNotTerrible.length)]; } } // Ultima spiaggia List nonTerribleMoves = availableLines.where((l) => !badClosingMoves.contains(l) && !iceTraps.contains(l)).toList(); if (nonTerribleMoves.isNotEmpty) { return nonTerribleMoves[random.nextInt(nonTerribleMoves.length)]; } return availableLines[random.nextInt(availableLines.length)]; } static _ClosureResult _checkClosure(GameBoard board, Line line, bool isInverted) { int netValue = 0; bool closesSomething = false; bool causesSwap = false; bool isIceTrap = false; for (var box in board.boxes) { if (box.type == BoxType.invisible) continue; if (box.top == line || box.bottom == line || box.left == line || box.right == line) { int linesCount = 0; if (box.top.owner != Player.none || box.top == line) linesCount++; if (box.bottom.owner != Player.none || box.bottom == line) linesCount++; if (box.left.owner != Player.none || box.left == line) linesCount++; if (box.right.owner != Player.none || box.right == line) linesCount++; if (linesCount == 4) { if (box.type == BoxType.ice && !line.isIceCracked) { isIceTrap = true; } else { closesSomething = true; if (box.type == BoxType.swap) { causesSwap = true; } else { int boxValue = 0; if (box.hiddenJokerOwner == board.currentPlayer) { boxValue = 2; } else { if (box.type == BoxType.gold) boxValue = 2; else if (box.type == BoxType.bomb) boxValue = -1; else if (box.type == BoxType.ice) boxValue = 0; else if (box.type == BoxType.multiplier) boxValue = 1; else boxValue = 1; } // LA MAGIA: Se il gioco è invertito, fare punti positivi viene calcolato come MALUS per l'IA! netValue += isInverted ? -boxValue : boxValue; } } } } } return _ClosureResult(closesSomething, netValue, causesSwap, isIceTrap); } static bool _isSafeMove(GameBoard board, Line line, int myScore, int oppScore, bool isInverted) { for (var box in board.boxes) { if (box.type == BoxType.invisible) continue; if (box.top == line || box.bottom == line || box.left == line || box.right == line) { int currentLinesCount = 0; if (box.top.owner != Player.none) currentLinesCount++; if (box.bottom.owner != Player.none) currentLinesCount++; if (box.left.owner != Player.none) currentLinesCount++; if (box.right.owner != Player.none) currentLinesCount++; if (currentLinesCount == 2) { int valueForOpponent = 0; if (box.type == BoxType.ice) { valueForOpponent = -5; } else if (box.type == BoxType.swap) { if (myScore < oppScore) { continue; // Sicuro lasciarlo: se lo prende perde i punti. } else { return false; // Pericoloso: se lo prende ci ruba il vantaggio! } } else if (box.hiddenJokerOwner == board.currentPlayer) { valueForOpponent = -1; } else { if (box.type == BoxType.gold) valueForOpponent = 2; else if (box.type == BoxType.bomb) valueForOpponent = -1; else if (box.type == BoxType.multiplier) valueForOpponent = 1; else valueForOpponent = 1; } // LA MAGIA 2: Se il tabellone è invertito, regalare un punto all'avversario è un'ottima esca! if (isInverted && box.type != BoxType.swap && box.type != BoxType.ice) { valueForOpponent = -valueForOpponent; } if (valueForOpponent < 0) { continue; // Mossa considerata sicura (trappola perfetta) } return false; } } } return true; } }