294 lines
No EOL
9.7 KiB
Dart
294 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 } // Aggiunti ice e 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; // NUOVO: Stato per il blocco di ghiaccio
|
|
|
|
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; // Il moltiplicatore e il ghiaccio non danno punti base
|
|
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;
|
|
|
|
// Variabili per il Moltiplicatore
|
|
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; // Nuova Scatola Ghiaccio
|
|
else if (level >= 15 && chance > 0.78 && chance <= 0.83) box.type = BoxType.multiplier; // Nuova Scatola x2
|
|
}
|
|
boxes.add(box);
|
|
}
|
|
}
|
|
|
|
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; // Si incrina ma non si chiude!
|
|
lastMove = actualLine;
|
|
if (forcedPlayer == null) currentPlayer = (currentPlayer == Player.red) ? Player.blue : Player.red;
|
|
else currentPlayer = (forcedPlayer == Player.red) ? Player.blue : Player.red;
|
|
return true; // Mossa valida, ma turno finito.
|
|
}
|
|
|
|
// Mossa normale o secondo colpo al ghiaccio
|
|
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) {
|
|
// Se la scatola chiusa dà punti e il giocatore ha un x2 attivo...
|
|
if (playerMakingMove == Player.red && redHasMultiplier) {
|
|
points *= 2;
|
|
redHasMultiplier = false; // Si consuma
|
|
} else if (playerMakingMove == Player.blue && blueHasMultiplier) {
|
|
points *= 2;
|
|
blueHasMultiplier = false; // Si consuma
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
} |