tetraq/lib/models/game_board.dart

200 lines
7.1 KiB
Dart
Raw Normal View History

2026-02-27 23:35:54 +01:00
// ===========================================================================
// FILE: lib/models/game_board.dart
// ===========================================================================
import 'dart:math';
enum Player { red, blue, none }
2026-03-04 14:27:15 +01:00
enum BoxType { normal, gold, bomb, invisible, swap, ice, multiplier }
// --- AGGIUNTA LA FORMA 3D ---
enum ArenaShape { classic, cross, donut, hourglass, chaos, test, pyramid3D }
2026-02-27 23:35:54 +01:00
class Dot {
final int x;
final int y;
2026-03-04 14:27:15 +01:00
final int z; // --- NUOVO: ALTEZZA 3D ---
Dot(this.x, this.y, {this.z = 0});
2026-02-27 23:35:54 +01:00
@override
2026-03-04 14:27:15 +01:00
bool operator ==(Object other) => identical(this, other) || other is Dot && runtimeType == other.runtimeType && x == other.x && y == other.y && z == other.z;
2026-02-27 23:35:54 +01:00
@override
2026-03-04 14:27:15 +01:00
int get hashCode => x.hashCode ^ y.hashCode ^ z.hashCode;
2026-02-27 23:35:54 +01:00
}
class Line {
final Dot p1;
final Dot p2;
Player owner = Player.none;
bool isPlayable = false;
2026-03-04 14:27:15 +01:00
bool isIceCracked = false;
2026-02-27 23:35:54 +01:00
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;
2026-03-04 14:27:15 +01:00
final int z; // --- NUOVO: PIANO DELLA SCATOLA ---
2026-02-27 23:35:54 +01:00
Player owner = Player.none;
late Line top, bottom, left, right;
BoxType type = BoxType.normal;
2026-03-01 20:59:06 +01:00
bool isRevealed = false;
Player? hiddenJokerOwner;
bool isJokerRevealed = false;
2026-03-04 14:27:15 +01:00
Box(this.x, this.y, {this.z = 0});
2026-02-27 23:35:54 +01:00
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;
}
2026-03-01 20:59:06 +01:00
int getCalculatedValue(Player closer) {
2026-03-04 14:27:15 +01:00
if (hiddenJokerOwner != null) return (closer == hiddenJokerOwner) ? 2 : -1;
2026-02-27 23:35:54 +01:00
if (type == BoxType.gold) return 2;
if (type == BoxType.bomb) return -1;
2026-03-04 14:27:15 +01:00
if (type == BoxType.swap || type == BoxType.ice || type == BoxType.multiplier) return 0;
// --- NUOVO: NELLA PIRAMIDE I PIANI ALTI VALGONO DI PIÙ! ---
return 1 + z;
2026-02-27 23:35:54 +01:00
}
}
class GameBoard {
final int radius;
final int level;
final int? seed;
final ArenaShape shape;
2026-03-01 20:59:06 +01:00
late int columns;
late int rows;
2026-02-27 23:35:54 +01:00
List<Dot> dots = [];
List<Line> lines = [];
List<Box> boxes = [];
Player currentPlayer = Player.red;
int scoreRed = 0;
int scoreBlue = 0;
bool isGameOver = false;
Line? lastMove;
2026-03-01 20:59:06 +01:00
bool redHasMultiplier = false;
bool blueHasMultiplier = false;
2026-02-27 23:35:54 +01:00
GameBoard({required this.radius, this.level = 1, this.seed, this.shape = ArenaShape.classic}) {
_generateBoard();
}
void _generateBoard() {
2026-03-04 14:27:15 +01:00
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);
2026-02-27 23:35:54 +01:00
}
}
2026-03-04 14:27:15 +01:00
}
_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;
2026-02-27 23:35:54 +01:00
}
}
}
2026-03-04 14:27:15 +01:00
}
2026-02-27 23:35:54 +01:00
2026-03-04 14:27:15 +01:00
// Sblocca i piani superiori solo se il "pavimento" è solido
void _updatePyramidPlayability() {
if (shape != ArenaShape.pyramid3D) return;
2026-02-27 23:35:54 +01:00
for (var box in boxes) {
2026-03-04 14:27:15 +01:00
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;
}
2026-02-27 23:35:54 +01:00
}
}
}
2026-03-04 14:27:15 +01:00
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;
2026-02-27 23:35:54 +01:00
}
Line _getOrAddLine(Dot a, Dot b) {
for (var line in lines) { if (line.connects(a, b)) return line; }
2026-03-04 14:27:15 +01:00
var newLine = Line(a, b); lines.add(newLine); return newLine;
2026-02-27 23:35:54 +01:00
}
bool playMove(Line lineToPlay, {Player? forcedPlayer}) {
if (isGameOver) return false;
Player playerMakingMove = forcedPlayer ?? currentPlayer;
Line? actualLine;
2026-03-04 14:27:15 +01:00
for (var l in lines) { if (l.connects(lineToPlay.p1, lineToPlay.p2)) { actualLine = l; break; } }
2026-02-27 23:35:54 +01:00
if (actualLine == null || actualLine.owner != Player.none || !actualLine.isPlayable) return false;
actualLine.owner = playerMakingMove;
lastMove = actualLine;
2026-03-01 20:59:06 +01:00
bool scoredPoint = false;
2026-02-27 23:35:54 +01:00
for (var box in boxes) {
if (box.owner == Player.none && box.isClosed()) {
2026-03-04 14:27:15 +01:00
box.owner = playerMakingMove; scoredPoint = true;
2026-03-01 20:59:06 +01:00
int points = box.getCalculatedValue(playerMakingMove);
2026-03-04 14:27:15 +01:00
if (playerMakingMove == Player.red) scoreRed += points; else scoreBlue += points;
2026-03-01 20:59:06 +01:00
}
2026-02-27 23:35:54 +01:00
}
2026-03-04 14:27:15 +01:00
if (shape == ArenaShape.pyramid3D) _updatePyramidPlayability(); // Ricalcola chi può giocare sopra!
2026-02-27 23:35:54 +01:00
2026-03-04 14:27:15 +01:00
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;
2026-02-27 23:35:54 +01:00
return true;
}
}