200 lines
No EOL
7.1 KiB
Dart
200 lines
No EOL
7.1 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 }
|
|
// --- AGGIUNTA LA FORMA 3D ---
|
|
enum ArenaShape { classic, cross, donut, hourglass, chaos, test, pyramid3D }
|
|
|
|
class Dot {
|
|
final int x;
|
|
final int y;
|
|
final int z; // --- NUOVO: ALTEZZA 3D ---
|
|
Dot(this.x, this.y, {this.z = 0});
|
|
|
|
@override
|
|
bool operator ==(Object other) => identical(this, other) || other is Dot && runtimeType == other.runtimeType && x == other.x && y == other.y && z == other.z;
|
|
@override
|
|
int get hashCode => x.hashCode ^ y.hashCode ^ z.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;
|
|
final int z; // --- NUOVO: PIANO DELLA SCATOLA ---
|
|
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, {this.z = 0});
|
|
|
|
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;
|
|
|
|
// --- NUOVO: NELLA PIRAMIDE I PIANI ALTI VALGONO DI PIÙ! ---
|
|
return 1 + z;
|
|
}
|
|
}
|
|
|
|
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() {
|
|
dots.clear(); lines.clear(); boxes.clear(); lastMove = null;
|
|
|
|
if (shape == ArenaShape.pyramid3D) {
|
|
// --- LOGICA GENERAZIONE PIRAMIDE 3D ---
|
|
columns = 4; rows = 4; // Base 4x4
|
|
int maxZ = 4; // 4 Piani (0, 1, 2, 3)
|
|
|
|
for (int z = 0; z < maxZ; z++) {
|
|
int currentLayerSize = columns - z; // Il piano si restringe man mano che sale
|
|
for (int y = 0; y < currentLayerSize; y++) {
|
|
for (int x = 0; x < currentLayerSize; x++) {
|
|
var box = Box(x, y, z: z);
|
|
// Più sali, più possibilità ci sono di trovare Oro o Bombe
|
|
if (z > 0 && Random().nextDouble() > 0.8) box.type = BoxType.gold;
|
|
if (z > 1 && Random().nextDouble() > 0.85) box.type = BoxType.bomb;
|
|
boxes.add(box);
|
|
|
|
Dot tl = _getOrAddDot(x, y, z);
|
|
Dot tr = _getOrAddDot(x + 1, y, z);
|
|
Dot bl = _getOrAddDot(x, y + 1, z);
|
|
Dot br = _getOrAddDot(x + 1, y + 1, z);
|
|
|
|
box.top = _getOrAddLine(tl, tr); box.bottom = _getOrAddLine(bl, br);
|
|
box.left = _getOrAddLine(tl, bl); box.right = _getOrAddLine(tr, br);
|
|
}
|
|
}
|
|
}
|
|
_updatePyramidPlayability(); // Blocchiamo i piani superiori!
|
|
} else {
|
|
// (Qui c'è il codice standard 2D che abbiamo lasciato invariato per non rompere nulla)
|
|
columns = shape == ArenaShape.chaos ? radius * 2 + 1 : (shape == ArenaShape.test ? 3 : radius * 2 + 1);
|
|
rows = shape == ArenaShape.chaos ? (radius * 3) + 2 : (shape == ArenaShape.test ? 3 : radius * 2 + 1);
|
|
|
|
for (int y = 0; y < rows; y++) {
|
|
for (int x = 0; x < columns; x++) {
|
|
var box = Box(x, y);
|
|
boxes.add(box);
|
|
Dot tl = _getOrAddDot(x, y, 0); Dot tr = _getOrAddDot(x + 1, y, 0);
|
|
Dot bl = _getOrAddDot(x, y + 1, 0); Dot br = _getOrAddDot(x + 1, y + 1, 0);
|
|
box.top = _getOrAddLine(tl, tr); box.bottom = _getOrAddLine(bl, br);
|
|
box.left = _getOrAddLine(tl, bl); box.right = _getOrAddLine(tr, br);
|
|
box.top.isPlayable = true; box.bottom.isPlayable = true; box.left.isPlayable = true; box.right.isPlayable = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sblocca i piani superiori solo se il "pavimento" è solido
|
|
void _updatePyramidPlayability() {
|
|
if (shape != ArenaShape.pyramid3D) return;
|
|
for (var box in boxes) {
|
|
if (box.z == 0) {
|
|
if (box.owner == Player.none) {
|
|
box.top.isPlayable = true; box.bottom.isPlayable = true; box.left.isPlayable = true; box.right.isPlayable = true;
|
|
}
|
|
} else {
|
|
// Cerca i 4 quadrati del piano di sotto che lo sorreggono
|
|
bool isSupported = true;
|
|
for (int dy = 0; dy <= 1; dy++) {
|
|
for (int dx = 0; dx <= 1; dx++) {
|
|
var supportBox = boxes.where((b) => b.z == box.z - 1 && b.x == box.x + dx && b.y == box.y + dy).firstOrNull;
|
|
if (supportBox == null || !supportBox.isClosed()) isSupported = false;
|
|
}
|
|
}
|
|
if (box.owner == Player.none) {
|
|
box.top.isPlayable = isSupported; box.bottom.isPlayable = isSupported;
|
|
box.left.isPlayable = isSupported; box.right.isPlayable = isSupported;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Dot _getOrAddDot(int x, int y, int z) {
|
|
for (var dot in dots) { if (dot.x == x && dot.y == y && dot.z == z) return dot; }
|
|
var newDot = Dot(x, y, z: z); 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 scoredPoint = false;
|
|
|
|
for (var box in boxes) {
|
|
if (box.owner == Player.none && box.isClosed()) {
|
|
box.owner = playerMakingMove; scoredPoint = true;
|
|
int points = box.getCalculatedValue(playerMakingMove);
|
|
if (playerMakingMove == Player.red) scoreRed += points; else scoreBlue += points;
|
|
}
|
|
}
|
|
|
|
if (shape == ArenaShape.pyramid3D) _updatePyramidPlayability(); // Ricalcola chi può giocare sopra!
|
|
|
|
if (lines.where((l) => l.isPlayable).every((l) => l.owner != Player.none)) isGameOver = true;
|
|
if (!scoredPoint && !isGameOver) currentPlayer = (playerMakingMove == Player.red) ? Player.blue : Player.red;
|
|
|
|
return true;
|
|
}
|
|
} |