195 lines
No EOL
7.1 KiB
Dart
195 lines
No EOL
7.1 KiB
Dart
// ===========================================================================
|
|
// 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; // NUOVO: Identifica le mosse suicide sul ghiaccio
|
|
|
|
_ClosureResult(this.closesSomething, this.netValue, this.causesSwap, this.isIceTrap);
|
|
}
|
|
|
|
class AIEngine {
|
|
static Line getBestMove(GameBoard board, int level) {
|
|
List<Line> 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;
|
|
|
|
List<Line> goodClosingMoves = [];
|
|
List<Line> badClosingMoves = [];
|
|
List<Line> iceTraps = []; // Le mosse da evitare assolutamente
|
|
|
|
for (var line in availableLines) {
|
|
var result = _checkClosure(board, line);
|
|
|
|
if (result.isIceTrap) {
|
|
iceTraps.add(line); // Segna la linea come trappola e passa alla prossima
|
|
continue;
|
|
}
|
|
|
|
if (result.closesSomething) {
|
|
if (result.causesSwap) {
|
|
if (myScore < oppScore) {
|
|
goodClosingMoves.add(line);
|
|
} else {
|
|
badClosingMoves.add(line);
|
|
}
|
|
} 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 (Ora include le esche del ghiaccio!) ---
|
|
List<Line> safeMoves = [];
|
|
for (var line in availableLines) {
|
|
if (!badClosingMoves.contains(line) && !goodClosingMoves.contains(line) && !iceTraps.contains(line) && _isSafeMove(board, line, myScore, oppScore)) {
|
|
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<Line> 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 prima del disastro: qualsiasi cosa tranne bombe e trappole ghiacciate
|
|
List<Line> nonTerribleMoves = availableLines.where((l) => !badClosingMoves.contains(l) && !iceTraps.contains(l)).toList();
|
|
if (nonTerribleMoves.isNotEmpty) {
|
|
return nonTerribleMoves[random.nextInt(nonTerribleMoves.length)];
|
|
}
|
|
|
|
// Se l'IA è messa all'angolo ed è costretta a suicidarsi... pesca a caso
|
|
return availableLines[random.nextInt(availableLines.length)];
|
|
}
|
|
|
|
static _ClosureResult _checkClosure(GameBoard board, Line line) {
|
|
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) {
|
|
// L'IA capisce che questa mossa non chiuderà il box, ma le farà perdere il turno.
|
|
isIceTrap = true;
|
|
} else {
|
|
closesSomething = true;
|
|
|
|
if (box.hiddenJokerOwner == board.currentPlayer) {
|
|
netValue += 2;
|
|
} else {
|
|
if (box.type == BoxType.gold) netValue += 2;
|
|
else if (box.type == BoxType.bomb) netValue -= 1;
|
|
else if (box.type == BoxType.swap) netValue += 0;
|
|
else if (box.type == BoxType.ice) netValue += 0; // Rompere il ghiaccio vale 0 punti, ma fa rigiocare
|
|
else if (box.type == BoxType.multiplier) netValue += 1; // Leggero boost per dare priorità al x2
|
|
else netValue += 1;
|
|
}
|
|
|
|
if (box.type == BoxType.swap) causesSwap = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return _ClosureResult(closesSomething, netValue, causesSwap, isIceTrap);
|
|
}
|
|
|
|
static bool _isSafeMove(GameBoard board, Line line, int myScore, int oppScore) {
|
|
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) {
|
|
// L'IA valuta cosa succede se lascia questa casella con 3 linee all'avversario
|
|
int valueForOpponent = 0;
|
|
|
|
if (box.type == BoxType.ice) {
|
|
// Il ghiaccio è la trappola perfetta. Lasciarlo con 3 linee spingerà l'avversario a incrinarlo e a perdere il turno.
|
|
// L'IA valuta questa mossa come SICURISSIMA!
|
|
valueForOpponent = -5;
|
|
} 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.swap) valueForOpponent = 0;
|
|
else if (box.type == BoxType.multiplier) valueForOpponent = 1;
|
|
else valueForOpponent = 1;
|
|
}
|
|
|
|
// Se per l'avversario è una trappola (bomba o ghiaccio), lascia pure la mossa libera
|
|
if (valueForOpponent < 0) {
|
|
continue;
|
|
}
|
|
|
|
if (box.type == BoxType.swap) {
|
|
if (myScore < oppScore) {
|
|
continue;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false; // La mossa regalerebbe punti, quindi NON è sicura
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
} |