tetraq/lib/logic/ai_engine.dart

202 lines
7.2 KiB
Dart
Raw Permalink Normal View History

2026-02-27 23:35:54 +01:00
// ===========================================================================
// 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;
2026-03-24 12:58:56 +01:00
final bool isIceTrap;
2026-03-23 01:00:01 +01:00
_ClosureResult(this.closesSomething, this.netValue, this.causesSwap, this.isIceTrap);
2026-02-27 23:35:54 +01:00
}
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;
2026-03-23 01:00:01 +01:00
// Più il livello è alto, più l'IA è "intelligente"
2026-02-27 23:35:54 +01:00
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;
2026-03-24 12:58:56 +01:00
// --- 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;
2026-02-27 23:35:54 +01:00
List<Line> goodClosingMoves = [];
List<Line> badClosingMoves = [];
2026-03-24 12:58:56 +01:00
List<Line> iceTraps = [];
2026-02-27 23:35:54 +01:00
for (var line in availableLines) {
2026-03-24 12:58:56 +01:00
var result = _checkClosure(board, line, isInverted);
2026-03-23 01:00:01 +01:00
if (result.isIceTrap) {
2026-03-24 12:58:56 +01:00
iceTraps.add(line);
2026-03-23 01:00:01 +01:00
continue;
}
2026-02-27 23:35:54 +01:00
if (result.closesSomething) {
if (result.causesSwap) {
if (myScore < oppScore) {
2026-03-24 12:58:56 +01:00
goodClosingMoves.add(line); // Se perdiamo, lo scambio è la mossa vincente!
2026-02-27 23:35:54 +01:00
} else {
2026-03-24 12:58:56 +01:00
badClosingMoves.add(line); // Se vinciamo, NON tocchiamo lo scambio!
2026-02-27 23:35:54 +01:00
}
} 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)];
}
}
2026-03-24 12:58:56 +01:00
// --- REGOLA 2: Mosse Sicure ---
2026-02-27 23:35:54 +01:00
List<Line> safeMoves = [];
for (var line in availableLines) {
2026-03-24 12:58:56 +01:00
if (!badClosingMoves.contains(line) && !goodClosingMoves.contains(line) && !iceTraps.contains(line) && _isSafeMove(board, line, myScore, oppScore, isInverted)) {
2026-02-27 23:35:54 +01:00
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) {
2026-03-23 01:00:01 +01:00
List<Line> riskyButNotTerrible = availableLines.where((l) => !badClosingMoves.contains(l) && !goodClosingMoves.contains(l) && !iceTraps.contains(l)).toList();
2026-02-27 23:35:54 +01:00
if (riskyButNotTerrible.isNotEmpty) {
return riskyButNotTerrible[random.nextInt(riskyButNotTerrible.length)];
}
}
2026-03-24 12:58:56 +01:00
// Ultima spiaggia
2026-03-23 01:00:01 +01:00
List<Line> nonTerribleMoves = availableLines.where((l) => !badClosingMoves.contains(l) && !iceTraps.contains(l)).toList();
2026-02-27 23:35:54 +01:00
if (nonTerribleMoves.isNotEmpty) {
return nonTerribleMoves[random.nextInt(nonTerribleMoves.length)];
}
return availableLines[random.nextInt(availableLines.length)];
}
2026-03-24 12:58:56 +01:00
static _ClosureResult _checkClosure(GameBoard board, Line line, bool isInverted) {
2026-02-27 23:35:54 +01:00
int netValue = 0;
bool closesSomething = false;
bool causesSwap = false;
2026-03-23 01:00:01 +01:00
bool isIceTrap = false;
2026-02-27 23:35:54 +01:00
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) {
2026-03-23 01:00:01 +01:00
if (box.type == BoxType.ice && !line.isIceCracked) {
isIceTrap = true;
2026-03-01 20:59:06 +01:00
} else {
2026-03-23 01:00:01 +01:00
closesSomething = true;
2026-03-01 20:59:06 +01:00
2026-03-24 12:58:56 +01:00
if (box.type == BoxType.swap) {
causesSwap = true;
2026-03-23 01:00:01 +01:00
} else {
2026-03-24 12:58:56 +01:00
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;
2026-03-23 01:00:01 +01:00
}
}
2026-02-27 23:35:54 +01:00
}
}
}
2026-03-23 01:00:01 +01:00
return _ClosureResult(closesSomething, netValue, causesSwap, isIceTrap);
2026-02-27 23:35:54 +01:00
}
2026-03-24 12:58:56 +01:00
static bool _isSafeMove(GameBoard board, Line line, int myScore, int oppScore, bool isInverted) {
2026-02-27 23:35:54 +01:00
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) {
2026-03-01 20:59:06 +01:00
int valueForOpponent = 0;
2026-03-23 01:00:01 +01:00
if (box.type == BoxType.ice) {
valueForOpponent = -5;
2026-03-24 12:58:56 +01:00
} 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!
}
2026-03-23 01:00:01 +01:00
} else if (box.hiddenJokerOwner == board.currentPlayer) {
2026-03-01 20:59:06 +01:00
valueForOpponent = -1;
} else {
if (box.type == BoxType.gold) valueForOpponent = 2;
else if (box.type == BoxType.bomb) valueForOpponent = -1;
2026-03-23 01:00:01 +01:00
else if (box.type == BoxType.multiplier) valueForOpponent = 1;
2026-03-01 20:59:06 +01:00
else valueForOpponent = 1;
}
2026-03-24 12:58:56 +01:00
// 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;
2026-02-27 23:35:54 +01:00
}
2026-03-01 20:59:06 +01:00
2026-03-24 12:58:56 +01:00
if (valueForOpponent < 0) {
continue; // Mossa considerata sicura (trappola perfetta)
2026-02-27 23:35:54 +01:00
}
2026-03-24 12:58:56 +01:00
return false;
2026-02-27 23:35:54 +01:00
}
}
}
return true;
}
}