245 lines
7.3 KiB
Dart
245 lines
7.3 KiB
Dart
|
|
// ===========================================================================
|
||
|
|
// FILE: lib/models/game_board.dart
|
||
|
|
// ===========================================================================
|
||
|
|
|
||
|
|
import 'dart:math';
|
||
|
|
|
||
|
|
enum Player { red, blue, none }
|
||
|
|
enum BoxType { normal, gold, bomb, invisible, swap }
|
||
|
|
|
||
|
|
// --- AGGIUNTO 'chaos' ---
|
||
|
|
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;
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
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 get value {
|
||
|
|
if (type == BoxType.gold) return 2;
|
||
|
|
if (type == BoxType.bomb) return -1;
|
||
|
|
if (type == BoxType.swap) return 0;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class GameBoard {
|
||
|
|
final int radius;
|
||
|
|
final int level;
|
||
|
|
final int? seed;
|
||
|
|
final ArenaShape shape;
|
||
|
|
|
||
|
|
List<Dot> dots = [];
|
||
|
|
List<Line> lines = [];
|
||
|
|
List<Box> boxes = [];
|
||
|
|
|
||
|
|
Player currentPlayer = Player.red;
|
||
|
|
int scoreRed = 0;
|
||
|
|
int scoreBlue = 0;
|
||
|
|
bool isGameOver = false;
|
||
|
|
|
||
|
|
Line? lastMove;
|
||
|
|
|
||
|
|
GameBoard({required this.radius, this.level = 1, this.seed, this.shape = ArenaShape.classic}) {
|
||
|
|
_generateBoard();
|
||
|
|
}
|
||
|
|
|
||
|
|
void _generateBoard() {
|
||
|
|
int size = radius * 2 + 1;
|
||
|
|
final random = seed != null ? Random(seed) : Random();
|
||
|
|
|
||
|
|
// Se è Caos, decidiamo quale algoritmo usare in base al seed
|
||
|
|
int chaosAlgorithm = random.nextInt(5);
|
||
|
|
|
||
|
|
dots.clear();
|
||
|
|
lines.clear();
|
||
|
|
boxes.clear();
|
||
|
|
lastMove = null;
|
||
|
|
|
||
|
|
for (int y = 0; y < size; y++) {
|
||
|
|
for (int x = 0; x < size; x++) {
|
||
|
|
var box = Box(x, y);
|
||
|
|
|
||
|
|
int dx = (x - radius).abs();
|
||
|
|
int dy = (y - radius).abs();
|
||
|
|
|
||
|
|
bool isVisible = (dx + dy) <= radius;
|
||
|
|
|
||
|
|
if (isVisible) {
|
||
|
|
switch (shape) {
|
||
|
|
case ArenaShape.classic:
|
||
|
|
break;
|
||
|
|
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;
|
||
|
|
case ArenaShape.chaos:
|
||
|
|
// --- GENERATORE PROCEDURALE (IL CAOS) ---
|
||
|
|
// Essendo basato su dx e dy, genererà sempre forme simmetriche a 4 vie!
|
||
|
|
if (chaosAlgorithm == 0) {
|
||
|
|
// Modello "Rete Frattale": Rimuove blocchi basati su operatori bitwise
|
||
|
|
if ((dx & dy) != 0) isVisible = false;
|
||
|
|
} else if (chaosAlgorithm == 1) {
|
||
|
|
// Modello "Quattro Pilastri": Svuota lunghe linee ma salva i centri
|
||
|
|
if (dx == 1 || dy == 1) {
|
||
|
|
if ((dx + dy) > 2 && (dx + dy) < radius) isVisible = false;
|
||
|
|
}
|
||
|
|
} else if (chaosAlgorithm == 2) {
|
||
|
|
// Modello "X-Treme": Taglia le diagonali perfette
|
||
|
|
if (dx == dy && dx > 0 && dx < radius) isVisible = false;
|
||
|
|
} else if (chaosAlgorithm == 3) {
|
||
|
|
// Modello "Scacchiera Nucleare": Alternanza precisa
|
||
|
|
if (dx % 2 == 1 && dy % 2 == 1) isVisible = false;
|
||
|
|
} else if (chaosAlgorithm == 4) {
|
||
|
|
// Modello "Anelli Frammentati": Buca gli anelli pari
|
||
|
|
if ((dx + dy) % 2 == 0 && (dx + dy) > 0) {
|
||
|
|
if (dx != 0 && dy != 0) isVisible = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Assicuriamoci che il punto centrale esista quasi sempre per connettere la mappa
|
||
|
|
if (dx == 0 && dy == 0) isVisible = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!isVisible) {
|
||
|
|
box.type = BoxType.invisible;
|
||
|
|
} else if (level > 1) {
|
||
|
|
double chance = random.nextDouble();
|
||
|
|
if (chance < 0.10) {
|
||
|
|
box.type = BoxType.gold;
|
||
|
|
} else if (chance > 0.90) {
|
||
|
|
box.type = BoxType.bomb;
|
||
|
|
} else if (level >= 5 && chance > 0.85 && chance <= 0.90) {
|
||
|
|
box.type = BoxType.swap;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
boxes.add(box);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Costruzione Linee (Identico a prima)
|
||
|
|
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;
|
||
|
|
|
||
|
|
actualLine.owner = playerMakingMove;
|
||
|
|
lastMove = actualLine;
|
||
|
|
|
||
|
|
bool boxedClosed = false;
|
||
|
|
bool triggeredSwap = false;
|
||
|
|
|
||
|
|
for (var box in boxes) {
|
||
|
|
if (box.owner == Player.none && box.isClosed()) {
|
||
|
|
box.owner = playerMakingMove;
|
||
|
|
boxedClosed = true;
|
||
|
|
|
||
|
|
if (playerMakingMove == Player.red) { scoreRed += box.value; }
|
||
|
|
else { scoreBlue += box.value; }
|
||
|
|
|
||
|
|
if (box.type == BoxType.swap) {
|
||
|
|
triggeredSwap = 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 (!boxedClosed && !isGameOver) { currentPlayer = (currentPlayer == Player.red) ? Player.blue : Player.red; }
|
||
|
|
} else {
|
||
|
|
if (!boxedClosed && !isGameOver) { currentPlayer = (forcedPlayer == Player.red) ? Player.blue : Player.red; }
|
||
|
|
else { currentPlayer = forcedPlayer; }
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|