tetraq/lib/models/game_board.dart
2026-03-24 12:58:56 +01:00

301 lines
No EOL
9.7 KiB
Dart

// ===========================================================================
// FILE: lib/models/game_board.dart
// ===========================================================================
import 'dart:math';
enum Player { red, blue, none }
enum BoxType { normal, gold, bomb, invisible, swap, ice, multiplier }
enum ArenaShape { classic, cross, donut, hourglass, chaos }
class Dot {
final int x;
final int y;
Dot(this.x, this.y);
@override
bool operator ==(Object other) => identical(this, other) || other is Dot && runtimeType == other.runtimeType && x == other.x && y == other.y;
@override
int get hashCode => x.hashCode ^ y.hashCode;
}
class Line {
final Dot p1;
final Dot p2;
Player owner = Player.none;
bool isPlayable = false;
bool isIceCracked = false;
Line(this.p1, this.p2);
bool connects(Dot a, Dot b) { return (p1 == a && p2 == b) || (p1 == b && p2 == a); }
}
class Box {
final int x;
final int y;
Player owner = Player.none;
late Line top, bottom, left, right;
BoxType type = BoxType.normal;
bool isRevealed = false;
Player? hiddenJokerOwner;
bool isJokerRevealed = false;
Box(this.x, this.y);
bool isClosed() {
if (type == BoxType.invisible) return false;
return top.owner != Player.none && bottom.owner != Player.none && left.owner != Player.none && right.owner != Player.none;
}
int getCalculatedValue(Player closer) {
if (hiddenJokerOwner != null) {
return (closer == hiddenJokerOwner) ? 2 : -1;
}
if (type == BoxType.gold) return 2;
if (type == BoxType.bomb) return -1;
if (type == BoxType.swap || type == BoxType.ice || type == BoxType.multiplier) return 0;
return 1;
}
}
class GameBoard {
final int radius;
final int level;
final int? seed;
final ArenaShape shape;
late int columns;
late int rows;
List<Dot> dots = [];
List<Line> lines = [];
List<Box> boxes = [];
Player currentPlayer = Player.red;
int scoreRed = 0;
int scoreBlue = 0;
bool isGameOver = false;
Line? lastMove;
bool redHasMultiplier = false;
bool blueHasMultiplier = false;
GameBoard({required this.radius, this.level = 1, this.seed, this.shape = ArenaShape.classic}) {
_generateBoard();
}
void _generateBoard() {
final random = seed != null ? Random(seed) : Random();
int chaosAlgorithm = random.nextInt(5);
if (shape == ArenaShape.chaos) {
columns = radius * 2 + 1;
rows = (radius * 3) + 2;
} else {
columns = radius * 2 + 1;
rows = radius * 2 + 1;
}
dots.clear();
lines.clear();
boxes.clear();
lastMove = null;
for (int y = 0; y < rows; y++) {
for (int x = 0; x < columns; x++) {
var box = Box(x, y);
bool isVisible = true;
if (shape != ArenaShape.chaos) {
int dx = (x - radius).abs();
int dy = (y - radius).abs();
isVisible = (dx + dy) <= radius;
if (isVisible) {
switch (shape) {
case ArenaShape.cross:
int spessoreBraccio = radius > 3 ? 1 : 0;
if (dx > spessoreBraccio && dy > spessoreBraccio) isVisible = false; break;
case ArenaShape.donut:
int dimensioneBuco = radius > 3 ? 2 : 1;
if ((dx + dy) <= dimensioneBuco) isVisible = false; break;
case ArenaShape.hourglass:
if (dx > dy) isVisible = false;
if (x == radius && y == radius) isVisible = true; break;
default: break;
}
}
} else {
double percentY = y / rows;
if (chaosAlgorithm == 0) {
isVisible = (x % 2 == 0) && (random.nextDouble() > 0.15);
} else if (chaosAlgorithm == 1) {
double chance = 0.2 + (percentY * 0.7);
isVisible = random.nextDouble() < chance;
} else if (chaosAlgorithm == 2) {
int midY = rows ~/ 2;
int distFromCenterY = (y - midY).abs();
int allowedWidth = (distFromCenterY / midY * radius).ceil() + 1;
int dx = (x - radius).abs();
isVisible = dx <= allowedWidth && random.nextDouble() > 0.1;
} else if (chaosAlgorithm == 3) {
isVisible = (y % 2 == 0) ? (x < columns - 1) : (x > 0);
if (random.nextDouble() > 0.8) isVisible = false;
} else if (chaosAlgorithm == 4) {
isVisible = random.nextDouble() > 0.45;
}
if (x == radius && y == rows ~/ 2) isVisible = true;
}
if (!isVisible) {
box.type = BoxType.invisible;
} else if (level > 1) {
double chance = random.nextDouble();
if (chance < 0.08) box.type = BoxType.gold;
else if (chance > 0.92) box.type = BoxType.bomb;
else if (level >= 5 && chance > 0.88 && chance <= 0.92) box.type = BoxType.swap;
else if (level >= 10 && chance > 0.83 && chance <= 0.88) box.type = BoxType.ice;
else if (level >= 15 && chance > 0.78 && chance <= 0.83) box.type = BoxType.multiplier;
}
boxes.add(box);
}
}
// =========================================================
// NUOVO BLOCCO: ELIMINAZIONE SCAMBI PARI
// =========================================================
int swapCount = boxes.where((b) => b.type == BoxType.swap).length;
if (swapCount > 0 && swapCount % 2 == 0) {
Box lastSwap = boxes.lastWhere((b) => b.type == BoxType.swap);
lastSwap.type = BoxType.normal;
}
// =========================================================
for (var box in boxes) {
Dot tl = _getOrAddDot(box.x, box.y);
Dot tr = _getOrAddDot(box.x + 1, box.y);
Dot bl = _getOrAddDot(box.x, box.y + 1);
Dot br = _getOrAddDot(box.x + 1, box.y + 1);
box.top = _getOrAddLine(tl, tr);
box.bottom = _getOrAddLine(bl, br);
box.left = _getOrAddLine(tl, bl);
box.right = _getOrAddLine(tr, br);
if (box.type != BoxType.invisible) {
box.top.isPlayable = true; box.bottom.isPlayable = true;
box.left.isPlayable = true; box.right.isPlayable = true;
}
}
}
Dot _getOrAddDot(int x, int y) {
for (var dot in dots) { if (dot.x == x && dot.y == y) return dot; }
var newDot = Dot(x, y);
dots.add(newDot); return newDot;
}
Line _getOrAddLine(Dot a, Dot b) {
for (var line in lines) { if (line.connects(a, b)) return line; }
var newLine = Line(a, b);
lines.add(newLine); return newLine;
}
bool playMove(Line lineToPlay, {Player? forcedPlayer}) {
if (isGameOver) return false;
Player playerMakingMove = forcedPlayer ?? currentPlayer;
Line? actualLine;
for (var l in lines) {
if (l.connects(lineToPlay.p1, lineToPlay.p2)) { actualLine = l; break; }
}
if (actualLine == null || actualLine.owner != Player.none || !actualLine.isPlayable) return false;
// --- LOGICA BLOCCO DI GHIACCIO ---
bool closesIce = false;
for (var box in boxes) {
if (box.type == BoxType.ice && box.owner == Player.none) {
int linesCount = 0;
if (box.top.owner != Player.none || box.top == actualLine) linesCount++;
if (box.bottom.owner != Player.none || box.bottom == actualLine) linesCount++;
if (box.left.owner != Player.none || box.left == actualLine) linesCount++;
if (box.right.owner != Player.none || box.right == actualLine) linesCount++;
if (linesCount == 4) closesIce = true;
}
}
if (closesIce && !actualLine.isIceCracked) {
actualLine.isIceCracked = true;
lastMove = actualLine;
if (forcedPlayer == null) currentPlayer = (currentPlayer == Player.red) ? Player.blue : Player.red;
else currentPlayer = (forcedPlayer == Player.red) ? Player.blue : Player.red;
return true;
}
actualLine.isIceCracked = false;
actualLine.owner = playerMakingMove;
lastMove = actualLine;
bool scoredPoint = false;
bool triggeredSwap = false;
for (var box in boxes) {
if (box.owner == Player.none && box.isClosed()) {
box.owner = playerMakingMove;
scoredPoint = true;
if (box.hiddenJokerOwner != null) box.isJokerRevealed = true;
int points = box.getCalculatedValue(playerMakingMove);
// --- LOGICA MOLTIPLICATORE x2 ---
if (box.type == BoxType.multiplier) {
if (playerMakingMove == Player.red) redHasMultiplier = true;
else blueHasMultiplier = true;
} else if (points != 0) {
if (playerMakingMove == Player.red && redHasMultiplier) {
points *= 2;
redHasMultiplier = false;
} else if (playerMakingMove == Player.blue && blueHasMultiplier) {
points *= 2;
blueHasMultiplier = false;
}
}
if (playerMakingMove == Player.red) { scoreRed += points; }
else { scoreBlue += points; }
if (box.type == BoxType.swap && box.hiddenJokerOwner == null) {
triggeredSwap = true;
}
}
if (box.type == BoxType.invisible && !box.isRevealed) {
if (box.top.owner != Player.none && box.bottom.owner != Player.none &&
box.left.owner != Player.none && box.right.owner != Player.none) {
box.isRevealed = true;
}
}
}
if (triggeredSwap) {
int temp = scoreRed; scoreRed = scoreBlue; scoreBlue = temp;
}
if (lines.where((l) => l.isPlayable).every((l) => l.owner != Player.none)) { isGameOver = true; }
if (forcedPlayer == null) {
if (!scoredPoint && !isGameOver) { currentPlayer = (currentPlayer == Player.red) ? Player.blue : Player.red; }
else if (scoredPoint && !isGameOver) { currentPlayer = playerMakingMove; }
} else {
if (!scoredPoint && !isGameOver) { currentPlayer = (forcedPlayer == Player.red) ? Player.blue : Player.red; }
else { currentPlayer = forcedPlayer; }
}
return true;
}
}