import 'dart:math'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; import 'package:jeweled/config/color_theme.dart'; import 'package:jeweled/config/default_global_settings.dart'; import 'package:jeweled/models/game/board.dart'; import 'package:jeweled/models/game/cell.dart'; import 'package:jeweled/models/game/cell_location.dart'; import 'package:jeweled/models/settings/settings_game.dart'; import 'package:jeweled/models/settings/settings_global.dart'; class Game { Game({ // Settings required this.gameSettings, required this.globalSettings, // State this.isRunning = false, this.isStarted = false, this.isFinished = false, this.animationInProgress = false, // Base data required this.board, // Game data required this.shuffledColors, this.availableBlocksCount = 0, this.score = 0, this.movesCount = 0, }); // Settings final GameSettings gameSettings; final GlobalSettings globalSettings; // State bool isRunning; bool isStarted; bool isFinished; bool animationInProgress; // Base data final Board board; // Game data List<int> shuffledColors; int availableBlocksCount; int score; int movesCount; factory Game.createEmpty() { return Game( // Settings gameSettings: GameSettings.createDefault(), globalSettings: GlobalSettings.createDefault(), // Base data board: Board.createEmpty(), // Game data shuffledColors: shuffleColors(DefaultGlobalSettings.defaultColorsThemeValue), ); } factory Game.createNew({ GameSettings? gameSettings, GlobalSettings? globalSettings, }) { final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); return Game( // Settings gameSettings: newGameSettings, globalSettings: newGlobalSettings, // State isRunning: true, // Base data board: Board.createRandom(newGameSettings), // Game data shuffledColors: shuffleColors(newGlobalSettings.colorsTheme), ); } bool get canBeResumed => isStarted && !isFinished; bool get gameWon => isRunning && isStarted && isFinished; static List<int> shuffleColors(final String colorsTheme) { List<int> values = List<int>.generate(ColorTheme.getColorsCount(colorsTheme), (i) => i + 1); values.shuffle(); return values; } void shuffleColorsAgain(final String colorsTheme) { shuffledColors = shuffleColors(colorsTheme); } Cell getCell(CellLocation cellLocation) { return board.cells[cellLocation.row][cellLocation.col]; } int? getCellValue(CellLocation cellLocation) { return getCell(cellLocation).value; } int? getCellValueShuffled(CellLocation cellLocation) { final int? value = getCell(cellLocation).value; return value != null ? shuffledColors[value - 1] : null; } void updateCellValue(CellLocation locationToUpdate, int? value) { board.cells[locationToUpdate.row][locationToUpdate.col].value = value; } void increaseMovesCount() { movesCount += 1; } void increaseScore(int? count) { score += (count ?? 0); } void updateAvailableBlocksCount() { availableBlocksCount = getAvailableBlocks(this).length; } List<CellLocation> getSiblingCells( final CellLocation referenceCellLocation, List<CellLocation> siblingCells, ) { final int boardSizeHorizontal = gameSettings.boardSizeValue; final int boardSizeVertical = gameSettings.boardSizeValue; final int? referenceValue = getCellValue(referenceCellLocation); for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { if (deltaCol == 0 || deltaRow == 0) { final int candidateRow = referenceCellLocation.row + deltaRow; final int candidateCol = referenceCellLocation.col + deltaCol; if ((candidateRow >= 0 && candidateRow < boardSizeVertical) && (candidateCol >= 0 && candidateCol < boardSizeHorizontal)) { final candidateLocation = CellLocation.go(candidateRow, candidateCol); if (getCellValue(candidateLocation) == referenceValue) { bool alreadyFound = false; for (int index = 0; index < siblingCells.length; index++) { if ((siblingCells[index].row == candidateRow) && (siblingCells[index].col == candidateCol)) { alreadyFound = true; } } if (!alreadyFound) { siblingCells.add(candidateLocation); siblingCells = getSiblingCells(candidateLocation, siblingCells); } } } } } } return siblingCells; } List<List<CellLocation>> getAvailableBlocks(final Game game) { final int boardSizeHorizontal = game.gameSettings.boardSizeValue; final int boardSizeVertical = game.gameSettings.boardSizeValue; final List<List<CellLocation>> blocks = []; for (int row = 0; row < boardSizeVertical; row++) { for (int col = 0; col < boardSizeHorizontal; col++) { final CellLocation cellLocation = CellLocation.go(row, col); if (game.getCellValue(cellLocation) != null) { // if current cell not already in a found block bool alreadyFound = false; for (List<CellLocation> foundBlock in blocks) { for (CellLocation foundBlockCell in foundBlock) { if ((foundBlockCell.row == row) && (foundBlockCell.col == col)) { alreadyFound = true; } } } if (!alreadyFound) { final List<CellLocation> block = game.getSiblingCells(cellLocation, []); if (block.length >= 3) { blocks.add(block); } } } } } return blocks; } bool hasAtLeastOneAvailableBlock() { final int boardSizeHorizontal = gameSettings.boardSizeValue; final int boardSizeVertical = gameSettings.boardSizeValue; for (int row = 0; row < boardSizeVertical; row++) { for (int col = 0; col < boardSizeHorizontal; col++) { final CellLocation cellLocation = CellLocation.go(row, col); if (getCellValue(cellLocation) != null) { final List<CellLocation> block = getSiblingCells(cellLocation, []); if (block.length >= 3) { // found one block => ok, not locked return true; } } } } printlog('Board is locked!'); return false; } bool isInBoard(CellLocation cell) { final int boardSize = gameSettings.boardSizeValue; if (cell.row > 0 && cell.row < boardSize && cell.col > 0 && cell.col < boardSize) { return true; } return false; } int getFillValue(CellLocation referenceCellLocation) { final int row = referenceCellLocation.row; final int col = referenceCellLocation.col; // build a list of values to pick one final List<int> values = []; // All eligible values (twice) final int maxValue = gameSettings.colorsCountValue; for (int i = 1; i <= maxValue; i++) { values.add(i); values.add(i); } // Add values of current col (twice) for (int r = 0; r <= gameSettings.boardSizeValue; r++) { if (isInBoard(CellLocation.go(r, col))) { final int? value = getCellValue(CellLocation.go(r, col)); if (value != null) { values.add(value); values.add(value); } } } // Add values of sibling cols (twice for top rows) for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { final int c = col + deltaCol; for (int r = 0; r < gameSettings.boardSizeValue; r++) { if (isInBoard(CellLocation.go(r, c))) { final int? value = getCellValue(CellLocation.go(r, c)); if (value != null) { values.add(value); if (row < gameSettings.boardSizeValue / 3) { values.add(value); } } } } } // Add values of sibling cells for (int deltaCol = -2; deltaCol <= 2; deltaCol++) { final int c = col + deltaCol; for (int deltaRow = -2; deltaRow <= 2; deltaRow++) { final int r = row + deltaRow; if (isInBoard(CellLocation.go(r, c))) { final int? value = getCellValue(CellLocation.go(r, c)); if (value != null) { values.add(value); } } } } // Pick random value from "ponderated" list return values[Random().nextInt(values.length)]; } void dump() { printlog(''); printlog('## Current game dump:'); printlog(''); printlog('$Game:'); printlog(' Settings'); gameSettings.dump(); globalSettings.dump(); printlog(' State'); printlog(' isRunning: $isRunning'); printlog(' isStarted: $isStarted'); printlog(' isFinished: $isFinished'); printlog(' animationInProgress: $animationInProgress'); printlog(' Base data'); board.dump(); printlog(' Game data'); printlog(' shuffledColors: $shuffledColors'); printlog(' availableBlocksCount: $availableBlocksCount'); printlog(' score: $score'); printlog(' movesCount: $movesCount'); printlog(''); } @override String toString() { return '$Game(${toJson()})'; } Map<String, dynamic>? toJson() { return <String, dynamic>{ // Settings 'gameSettings': gameSettings.toJson(), 'globalSettings': globalSettings.toJson(), // State 'isRunning': isRunning, 'isStarted': isStarted, 'isFinished': isFinished, 'animationInProgress': animationInProgress, // Base data 'board': board.toJson(), // Game data 'shuffledColors': shuffledColors, 'availableBlocksCount': availableBlocksCount, 'score': score, 'movesCount': movesCount, }; } }