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