Select Git revision
-
Benoît Harrault authoredBenoît Harrault authored
game.dart 9.83 KiB
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,
};
}
}