// =========================================================================== // 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 dots = []; List lines = []; List 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; } }