2024-06-16 23:56:48 +02:00
|
|
|
import 'dart:math';
|
|
|
|
|
2024-06-19 23:40:31 +02:00
|
|
|
import 'package:bubbletwist/game/stone/quad_special_stone.dart';
|
2024-06-17 16:24:51 +02:00
|
|
|
import 'package:bubbletwist/game/stone/special_stone.dart';
|
2024-06-19 23:40:31 +02:00
|
|
|
import 'package:bubbletwist/game/stone/triple_special_stone.dart';
|
2024-06-16 23:56:48 +02:00
|
|
|
|
2024-06-17 16:24:51 +02:00
|
|
|
import '../enums/stone_color.dart';
|
|
|
|
import 'game.dart';
|
|
|
|
import 'stone/penta_special_stone.dart';
|
|
|
|
import 'stone/stone.dart';
|
|
|
|
import 'stone/stone_location.dart';
|
2024-06-16 23:56:48 +02:00
|
|
|
|
|
|
|
class Board {
|
|
|
|
static const int boardSize = 8;
|
|
|
|
final Game game;
|
2024-06-19 23:40:31 +02:00
|
|
|
late final List<List<Stone?>> stones =
|
|
|
|
List.generate(boardSize, (i) => List.generate(boardSize, (j) => Stone()));
|
|
|
|
bool specialStonesEnabled = false;
|
2024-06-16 23:56:48 +02:00
|
|
|
|
|
|
|
Board(this.game) {
|
|
|
|
// The map generates with pairs of 3+ stones already in place.
|
|
|
|
// To fix that we just let the game remove all 3+ stone pairs until none are left
|
2024-06-19 23:40:31 +02:00
|
|
|
while (checkBoard()) {}
|
2024-06-16 23:56:48 +02:00
|
|
|
}
|
2024-06-19 23:40:31 +02:00
|
|
|
|
2024-06-16 23:56:48 +02:00
|
|
|
void updateBoard() {
|
|
|
|
game.updateBoard();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool swapStones(StoneLocation sl1, StoneLocation sl2) {
|
|
|
|
// Check if the two stones that have been clicked are next to each other
|
|
|
|
if (!(sl1.row >= sl2.row - 1 && sl1.row <= sl2.row + 1) ||
|
|
|
|
!(sl1.column >= sl2.column - 1 && sl1.column <= sl2.column + 1)) {
|
|
|
|
return false; // If they are not next to each other just abort
|
|
|
|
}
|
|
|
|
|
|
|
|
final s1 = game.getStone(sl1);
|
|
|
|
final ss1 = s1 is PentaSpecialStone ? s1 : null;
|
|
|
|
if (ss1 != null) {
|
|
|
|
ss1.stoneColor = getStone(sl2)!.stoneColor;
|
|
|
|
performSpecialStone(ss1, sl1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
final s2 = game.getStone(sl2);
|
|
|
|
final ss2 = s2 is PentaSpecialStone ? s2 : null;
|
|
|
|
if (ss2 != null) {
|
|
|
|
ss2.stoneColor = getStone(sl1)!.stoneColor;
|
|
|
|
performSpecialStone(ss2, sl2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Swap stones
|
|
|
|
swap(sl1, sl2);
|
|
|
|
if (checkBoard()) {
|
|
|
|
// Check if swapping was a valid move
|
|
|
|
// If Swap is valid, Stones have been removed and others have to fall down
|
|
|
|
applyGravity();
|
|
|
|
while (checkBoard()) {
|
|
|
|
// Then we have to keep checking until no more 3+ stones are created by falling stones
|
|
|
|
applyGravity();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
// If Swap is invalid, the stones get swapped back
|
|
|
|
swap(sl1, sl2);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool checkBoard() {
|
|
|
|
bool stuffDeleted = false;
|
|
|
|
|
|
|
|
// FOR HORIZONTALS
|
|
|
|
for (int row = 0; row < boardSize; ++row) {
|
|
|
|
// First go through each row
|
|
|
|
int startPosition = 0;
|
|
|
|
int counter = 1;
|
|
|
|
for (int column = 1; column < boardSize; ++column) {
|
|
|
|
// And then through each column
|
|
|
|
if (stones[row][column] == null || stones[row][column - 1] == null) {
|
|
|
|
// Ignore deleted stones
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
final colorsAreSame = stones[row][column]!.stoneColor ==
|
|
|
|
stones[row][column - 1]!.stoneColor;
|
|
|
|
|
|
|
|
// If both stones have the same color increase counter
|
|
|
|
if (colorsAreSame) {
|
|
|
|
counter++;
|
|
|
|
}
|
|
|
|
// If they are not the same color or we reach the end of the Board,
|
|
|
|
// check the counter to see if the last 3+ stones had the same color
|
|
|
|
if (!colorsAreSame || column == boardSize - 1) {
|
|
|
|
if (counter >= 3) {
|
|
|
|
// And if we had 3+ stones, remove them
|
|
|
|
if (game.isRunning()) {
|
|
|
|
game.addPoints(counter);
|
|
|
|
game.addTime(3);
|
|
|
|
}
|
|
|
|
for (int k = 0; k < counter; ++k) {
|
|
|
|
final st = StoneLocation(row: row, column: startPosition + k);
|
|
|
|
removeStone(st);
|
|
|
|
stuffDeleted = true;
|
|
|
|
}
|
2024-06-19 23:40:31 +02:00
|
|
|
if (counter == 3 && game.isRunning() && specialStonesEnabled) {
|
|
|
|
stones[row][startPosition] = TripleSpecialStone(this);
|
2024-06-16 23:56:48 +02:00
|
|
|
}
|
2024-06-19 23:40:31 +02:00
|
|
|
if (counter == 4 && game.isRunning() && specialStonesEnabled) {
|
|
|
|
stones[row][startPosition] = QuadSpecialStone(this, false);
|
2024-06-16 23:56:48 +02:00
|
|
|
}
|
2024-06-19 23:40:31 +02:00
|
|
|
if (counter == 5 && game.isRunning() && specialStonesEnabled) {
|
2024-06-16 23:56:48 +02:00
|
|
|
stones[row][startPosition] = PentaSpecialStone(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Reset counter and start position after each color change
|
|
|
|
counter = 1;
|
|
|
|
startPosition = column;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FOR VERTICALS
|
|
|
|
for (int column = 0; column < boardSize; ++column) {
|
|
|
|
// First go through each column
|
|
|
|
int startPosition = 0;
|
|
|
|
int counter = 1;
|
|
|
|
for (int row = 1; row < boardSize; ++row) {
|
|
|
|
// And then through each row
|
|
|
|
if (stones[row][column] == null || stones[row - 1][column] == null) {
|
|
|
|
// Ignore deleted stones
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If both stones have the same color increase counter
|
|
|
|
final colorsAreSame = stones[row][column]!.stoneColor ==
|
|
|
|
stones[row - 1][column]!.stoneColor;
|
|
|
|
if (colorsAreSame) {
|
|
|
|
counter++;
|
|
|
|
}
|
|
|
|
// If they are not the same color or we reach the end of the Board,
|
|
|
|
// check the counter to see if the last 3+ stones had the same color
|
|
|
|
if (!colorsAreSame || row == boardSize - 1) {
|
|
|
|
if (counter >= 3) {
|
|
|
|
//And if we had 3+ stones, remove them
|
|
|
|
if (game.isRunning()) {
|
|
|
|
game.addPoints(counter);
|
|
|
|
game.addTime(3);
|
|
|
|
}
|
|
|
|
for (int k = 0; k < counter; ++k) {
|
|
|
|
final st = StoneLocation(row: startPosition + k, column: column);
|
|
|
|
removeStone(st);
|
|
|
|
stuffDeleted = true;
|
|
|
|
}
|
2024-06-19 23:40:31 +02:00
|
|
|
if (counter == 3 && game.isRunning() && specialStonesEnabled) {
|
|
|
|
stones[startPosition][column] = TripleSpecialStone(this);
|
2024-06-16 23:56:48 +02:00
|
|
|
}
|
2024-06-19 23:40:31 +02:00
|
|
|
if (counter == 4 && game.isRunning() && specialStonesEnabled) {
|
|
|
|
stones[startPosition][column] = QuadSpecialStone(this, true);
|
2024-06-16 23:56:48 +02:00
|
|
|
}
|
2024-06-19 23:40:31 +02:00
|
|
|
if (counter == 5 && game.isRunning() && specialStonesEnabled) {
|
2024-06-16 23:56:48 +02:00
|
|
|
stones[startPosition][column] = PentaSpecialStone(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Reset counter and start position after each color change
|
|
|
|
counter = 1;
|
|
|
|
startPosition = row;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (stuffDeleted) {
|
|
|
|
// If stuff has been deleted we have to update the Gui
|
|
|
|
game.updateBoard();
|
|
|
|
}
|
|
|
|
return stuffDeleted;
|
|
|
|
}
|
|
|
|
|
|
|
|
void removeStone(StoneLocation sl) {
|
|
|
|
if (sl.row < 0 ||
|
|
|
|
sl.row >= boardSize ||
|
|
|
|
sl.column < 0 ||
|
|
|
|
sl.column >= boardSize) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!game.isRunning()) {
|
|
|
|
// If game is not running, just give the stones a new color
|
|
|
|
stones[sl.row][sl.column]!.stoneColor =
|
2024-06-19 23:40:31 +02:00
|
|
|
StoneColors.values[Random().nextInt(StoneColors.values.length - 1)];
|
2024-06-16 23:56:48 +02:00
|
|
|
} else {
|
|
|
|
// Otherwise mark stones as deleted
|
|
|
|
stones[sl.row][sl.column] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void applyGravity() {
|
|
|
|
bool stonesHaveFallenDown;
|
|
|
|
// Move Stones down 1 row if the stone below them is marked as deleted
|
|
|
|
do {
|
|
|
|
stonesHaveFallenDown = false;
|
|
|
|
for (int column = 0; column < boardSize; ++column) {
|
|
|
|
for (int row = boardSize - 1; row > 0; --row) {
|
|
|
|
if (stones[row][column] == null) {
|
|
|
|
if (stones[row - 1][column] == null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stones[row][column] = stones[row - 1][column];
|
|
|
|
stones[row - 1][column] = null;
|
|
|
|
stonesHaveFallenDown = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (
|
|
|
|
stonesHaveFallenDown); // Continue doing so until all deleted Stones are at the top most position
|
|
|
|
|
|
|
|
// Then generate new stones that rain from the sky)
|
2024-06-19 23:40:31 +02:00
|
|
|
for (int i = 0; i < boardSize; i++) {
|
|
|
|
for (int j = 0; j < boardSize; j++) {
|
|
|
|
if (stones[i][j] == null) {
|
2024-06-17 10:48:31 +02:00
|
|
|
stones[i][j] = Stone();
|
2024-06-16 23:56:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
game.updateBoard();
|
|
|
|
}
|
|
|
|
|
|
|
|
void performSpecialStone(SpecialStone ss, StoneLocation sl) {
|
|
|
|
//Delete Stones
|
|
|
|
ss.performSpecialStoneAction();
|
|
|
|
removeStone(sl);
|
|
|
|
//show Deleted Stones
|
|
|
|
updateBoard();
|
|
|
|
//Make Stones fall down into new "holes"
|
|
|
|
applyGravity();
|
|
|
|
updateBoard(); //Show the Gravity Effect
|
|
|
|
while (checkBoard()) {
|
|
|
|
// Then we have to keep checking until no more 3+ stones are created by falling stones
|
|
|
|
applyGravity();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void swap(StoneLocation sl1, StoneLocation sl2) {
|
|
|
|
final tmp = stones[sl1.row][sl1.column];
|
|
|
|
stones[sl1.row][sl1.column] = stones[sl2.row][sl2.column];
|
|
|
|
stones[sl2.row][sl2.column] = tmp;
|
|
|
|
game.updateBoard();
|
|
|
|
}
|
|
|
|
|
|
|
|
Stone? getStone(StoneLocation sl) {
|
|
|
|
return stones[sl.row][sl.column];
|
|
|
|
}
|
|
|
|
}
|