Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 28-add-high-scores
  • 29-use-real-board-painter-to-draw-parameters-items
  • 32-add-block-size-limit-parameter
  • 39-improve-app-metadata
  • master
  • Release_0.0.10_10
  • Release_0.0.11_11
  • Release_0.0.12_12
  • Release_0.0.13_13
  • Release_0.0.14_14
  • Release_0.0.15_15
  • Release_0.0.16_16
  • Release_0.0.17_17
  • Release_0.0.18_18
  • Release_0.0.19_19
  • Release_0.0.1_1
  • Release_0.0.20_20
  • Release_0.0.21_21
  • Release_0.0.22_22
  • Release_0.0.23_23
  • Release_0.0.24_24
  • Release_0.0.25_25
  • Release_0.0.26_26
  • Release_0.0.27_27
  • Release_0.0.28_28
  • Release_0.0.29_29
  • Release_0.0.2_2
  • Release_0.0.30_30
  • Release_0.0.31_31
  • Release_0.0.3_3
  • Release_0.0.4_4
  • Release_0.0.5_5
  • Release_0.0.6_6
  • Release_0.0.7_7
  • Release_0.0.8_8
  • Release_0.0.9_9
  • Release_0.1.0_32
  • Release_0.1.1_33
  • Release_0.2.0_34
  • Release_0.2.1_35
  • Release_0.3.0_36
  • Release_0.3.1_37
  • Release_0.4.0_38
  • Release_0.4.1_39
  • Release_0.4.2_40
  • Release_0.5.0_41
  • Release_0.6.0_42
  • Release_0.7.0_43
  • Release_0.8.0_44
  • Release_0.8.1_45
  • Release_0.8.2_46
  • Release_0.9.0_47
52 results

Target

Select target project
  • android/org.benoitharrault.jeweled
1 result
Select Git revision
  • 28-add-high-scores
  • 29-use-real-board-painter-to-draw-parameters-items
  • 32-add-block-size-limit-parameter
  • 39-improve-app-metadata
  • master
  • Release_0.0.10_10
  • Release_0.0.11_11
  • Release_0.0.12_12
  • Release_0.0.13_13
  • Release_0.0.14_14
  • Release_0.0.15_15
  • Release_0.0.16_16
  • Release_0.0.17_17
  • Release_0.0.18_18
  • Release_0.0.19_19
  • Release_0.0.1_1
  • Release_0.0.20_20
  • Release_0.0.21_21
  • Release_0.0.22_22
  • Release_0.0.23_23
  • Release_0.0.24_24
  • Release_0.0.25_25
  • Release_0.0.26_26
  • Release_0.0.27_27
  • Release_0.0.28_28
  • Release_0.0.29_29
  • Release_0.0.2_2
  • Release_0.0.30_30
  • Release_0.0.31_31
  • Release_0.0.3_3
  • Release_0.0.4_4
  • Release_0.0.5_5
  • Release_0.0.6_6
  • Release_0.0.7_7
  • Release_0.0.8_8
  • Release_0.0.9_9
  • Release_0.1.0_32
  • Release_0.1.1_33
  • Release_0.2.0_34
  • Release_0.2.1_35
  • Release_0.3.0_36
  • Release_0.3.1_37
  • Release_0.4.0_38
  • Release_0.4.1_39
  • Release_0.4.2_40
  • Release_0.5.0_41
  • Release_0.6.0_42
  • Release_0.7.0_43
  • Release_0.8.0_44
  • Release_0.8.1_45
  • Release_0.8.2_46
  • Release_0.9.0_47
52 results
Show changes
Showing
with 1287 additions and 358 deletions
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:jeweled/models/settings_global.dart';
import 'package:jeweled/utils/tools.dart';
part 'settings_global_state.dart';
class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault()));
void setValues({
int? colorsTheme,
int? graphicTheme,
}) {
emit(
GlobalSettingsState(
settings: GlobalSettings(
colorsTheme: colorsTheme ?? state.settings.colorsTheme,
graphicTheme: graphicTheme ?? state.settings.graphicTheme,
),
),
);
}
int getParameterValue(String code) {
switch (code) {
case 'colorsTheme':
return GlobalSettings.getColorsThemeValueFromUnsafe(state.settings.colorsTheme);
case 'graphicTheme':
return GlobalSettings.getGraphicThemeValueFromUnsafe(state.settings.graphicTheme);
}
return 0;
}
void setParameterValue(String code, int value) {
printlog('GlobalSettingsCubit.setParameterValue');
printlog('code: $code / value: $value');
int colorsTheme = code == 'colorsTheme' ? value : getParameterValue('colorsTheme');
int graphicTheme = code == 'graphicTheme' ? value : getParameterValue('graphicTheme');
setValues(
colorsTheme: colorsTheme,
graphicTheme: graphicTheme,
);
}
@override
GlobalSettingsState? fromJson(Map<String, dynamic> json) {
int colorsTheme = json['colorsTheme'] as int;
int graphicTheme = json['graphicTheme'] as int;
return GlobalSettingsState(
settings: GlobalSettings(
colorsTheme: colorsTheme,
graphicTheme: graphicTheme,
),
);
}
@override
Map<String, dynamic>? toJson(GlobalSettingsState state) {
return <String, dynamic>{
'colorsTheme': state.settings.colorsTheme,
'graphicTheme': state.settings.graphicTheme,
};
}
}
part of 'settings_global_cubit.dart';
@immutable
class GlobalSettingsState extends Equatable {
const GlobalSettingsState({
required this.settings,
});
final GlobalSettings settings;
@override
List<dynamic> get props => <dynamic>[
settings,
];
Map<String, dynamic> get values => <String, dynamic>{
'settings': settings,
};
}
...@@ -8,8 +8,9 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; ...@@ -8,8 +8,9 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:jeweled/config/theme.dart'; import 'package:jeweled/config/theme.dart';
import 'package:jeweled/cubit/settings_cubit.dart';
import 'package:jeweled/cubit/game_cubit.dart'; import 'package:jeweled/cubit/game_cubit.dart';
import 'package:jeweled/cubit/settings_game_cubit.dart';
import 'package:jeweled/cubit/settings_global_cubit.dart';
import 'package:jeweled/ui/skeleton.dart'; import 'package:jeweled/ui/skeleton.dart';
void main() async { void main() async {
...@@ -44,7 +45,8 @@ class MyApp extends StatelessWidget { ...@@ -44,7 +45,8 @@ class MyApp extends StatelessWidget {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<GameCubit>(create: (context) => GameCubit()), BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
], ],
child: MaterialApp( child: MaterialApp(
title: 'Jeweled', title: 'Jeweled',
......
...@@ -8,6 +8,11 @@ class CellLocation { ...@@ -8,6 +8,11 @@ class CellLocation {
}); });
factory CellLocation.go(int row, int col) { factory CellLocation.go(int row, int col) {
return new CellLocation(col: col, row: row); return CellLocation(col: col, row: row);
}
@override
String toString() {
return 'CellLocation(col: $col, row: $row)';
} }
} }
import 'dart:math'; import 'dart:math';
import 'package:jeweled/config/default_global_settings.dart';
import 'package:jeweled/models/game_board.dart'; import 'package:jeweled/models/game_board.dart';
import 'package:jeweled/models/game_cell.dart'; import 'package:jeweled/models/game_cell.dart';
import 'package:jeweled/models/cell_location.dart'; import 'package:jeweled/models/cell_location.dart';
import 'package:jeweled/models/game_settings.dart'; import 'package:jeweled/models/settings_game.dart';
import 'package:jeweled/models/settings_global.dart';
import 'package:jeweled/utils/color_theme.dart';
import 'package:jeweled/utils/tools.dart';
class Game { class Game {
final GameBoard board; final GameBoard board;
final GameSettings settings; final GameSettings gameSettings;
final GlobalSettings globalSettings;
List<int> shuffledColors = [];
bool isRunning = false; bool isRunning = false;
bool isFinished = false; bool isFinished = false;
int availableBlocksCount = 0; int availableBlocksCount = 0;
...@@ -16,7 +22,9 @@ class Game { ...@@ -16,7 +22,9 @@ class Game {
Game({ Game({
required this.board, required this.board,
required this.settings, required this.gameSettings,
required this.globalSettings,
required this.shuffledColors,
this.isRunning = false, this.isRunning = false,
this.isFinished = false, this.isFinished = false,
this.availableBlocksCount = 0, this.availableBlocksCount = 0,
...@@ -27,63 +35,88 @@ class Game { ...@@ -27,63 +35,88 @@ class Game {
factory Game.createNull() { factory Game.createNull() {
return Game( return Game(
board: GameBoard.createNull(), board: GameBoard.createNull(),
settings: GameSettings.createDefault(), gameSettings: GameSettings.createDefault(),
globalSettings: GlobalSettings.createDefault(),
shuffledColors: shuffleColors(DefaultGlobalSettings.defaultColorsThemeValue),
); );
} }
factory Game.createNew({GameSettings? gameSettings}) { factory Game.createNew({
GameSettings settings = gameSettings ?? GameSettings.createDefault(); GameSettings? gameSettings,
GlobalSettings? globalSettings,
}) {
GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault();
GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault();
return Game( return Game(
board: GameBoard.createRandom(settings), board: GameBoard.createRandom(newGameSettings),
settings: settings, gameSettings: newGameSettings,
globalSettings: newGlobalSettings,
shuffledColors: shuffleColors(newGlobalSettings.colorsTheme),
isRunning: true, isRunning: true,
); );
} }
static List<int> shuffleColors(final int colorsTheme) {
List<int> values =
List<int>.generate(ColorTheme.getColorsCount(colorsTheme), (i) => i + 1);
values.shuffle();
return values;
}
void shuffleColorsAgain(final int colorsTheme) {
shuffledColors = shuffleColors(colorsTheme);
}
GameCell getCell(CellLocation cellLocation) { GameCell getCell(CellLocation cellLocation) {
return this.board.cells[cellLocation.row][cellLocation.col]; return board.cells[cellLocation.row][cellLocation.col];
} }
int? getCellValue(CellLocation cellLocation) { int? getCellValue(CellLocation cellLocation) {
return this.getCell(cellLocation).value; 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) { void updateCellValue(CellLocation locationToUpdate, int? value) {
this.board.cells[locationToUpdate.row][locationToUpdate.col].value = value; board.cells[locationToUpdate.row][locationToUpdate.col].value = value;
} }
void increaseMovesCount() { void increaseMovesCount() {
this.movesCount += 1; movesCount += 1;
} }
void increaseScore(int? count) { void increaseScore(int? count) {
this.score += (count ?? 0); score += (count ?? 0);
} }
void updateAvailableBlocksCount() { void updateAvailableBlocksCount() {
this.availableBlocksCount = this.getAvailableBlocks(this).length; availableBlocksCount = getAvailableBlocks(this).length;
} }
void updateGameIsRunning(bool gameIsRunning) { void updateGameIsRunning(bool gameIsRunning) {
this.isRunning = gameIsRunning; isRunning = gameIsRunning;
} }
void updateGameIsFinished(bool gameIsFinished) { void updateGameIsFinished(bool gameIsFinished) {
this.isFinished = gameIsFinished; isFinished = gameIsFinished;
} }
List<CellLocation> getSiblingCells( List<CellLocation> getSiblingCells(
final CellLocation referenceCellLocation, final CellLocation referenceCellLocation,
List<CellLocation> siblingCells, List<CellLocation> siblingCells,
) { ) {
final int boardSizeHorizontal = this.settings.boardSize; final int boardSizeHorizontal = gameSettings.boardSize;
final int boardSizeVertical = this.settings.boardSize; final int boardSizeVertical = gameSettings.boardSize;
final int? referenceValue = this.getCellValue(referenceCellLocation); final int? referenceValue = getCellValue(referenceCellLocation);
for (var deltaRow = -1; deltaRow <= 1; deltaRow++) { for (int deltaRow = -1; deltaRow <= 1; deltaRow++) {
for (var deltaCol = -1; deltaCol <= 1; deltaCol++) { for (int deltaCol = -1; deltaCol <= 1; deltaCol++) {
if (deltaCol == 0 || deltaRow == 0) { if (deltaCol == 0 || deltaRow == 0) {
final int candidateRow = referenceCellLocation.row + deltaRow; final int candidateRow = referenceCellLocation.row + deltaRow;
final int candidateCol = referenceCellLocation.col + deltaCol; final int candidateCol = referenceCellLocation.col + deltaCol;
...@@ -92,9 +125,9 @@ class Game { ...@@ -92,9 +125,9 @@ class Game {
(candidateCol >= 0 && candidateCol < boardSizeHorizontal)) { (candidateCol >= 0 && candidateCol < boardSizeHorizontal)) {
final candidateLocation = CellLocation.go(candidateRow, candidateCol); final candidateLocation = CellLocation.go(candidateRow, candidateCol);
if (this.getCellValue(candidateLocation) == referenceValue) { if (getCellValue(candidateLocation) == referenceValue) {
bool alreadyFound = false; bool alreadyFound = false;
for (var index = 0; index < siblingCells.length; index++) { for (int index = 0; index < siblingCells.length; index++) {
if ((siblingCells[index].row == candidateRow) && if ((siblingCells[index].row == candidateRow) &&
(siblingCells[index].col == candidateCol)) { (siblingCells[index].col == candidateCol)) {
alreadyFound = true; alreadyFound = true;
...@@ -114,25 +147,25 @@ class Game { ...@@ -114,25 +147,25 @@ class Game {
} }
List<List<CellLocation>> getAvailableBlocks(final Game game) { List<List<CellLocation>> getAvailableBlocks(final Game game) {
final int boardSizeHorizontal = game.settings.boardSize; final int boardSizeHorizontal = game.gameSettings.boardSize;
final int boardSizeVertical = game.settings.boardSize; final int boardSizeVertical = game.gameSettings.boardSize;
final List<List<CellLocation>> blocks = []; final List<List<CellLocation>> blocks = [];
for (var row = 0; row < boardSizeVertical; row++) { for (int row = 0; row < boardSizeVertical; row++) {
for (var col = 0; col < boardSizeHorizontal; col++) { for (int col = 0; col < boardSizeHorizontal; col++) {
final CellLocation cellLocation = CellLocation.go(row, col); final CellLocation cellLocation = CellLocation.go(row, col);
if (game.getCellValue(cellLocation) != null) { if (game.getCellValue(cellLocation) != null) {
// if current cell not already in a found block // if current cell not already in a found block
bool alreadyFound = false; bool alreadyFound = false;
blocks.forEach((List<CellLocation> foundBlock) { for (List<CellLocation> foundBlock in blocks) {
foundBlock.forEach((CellLocation foundBlockCell) { for (CellLocation foundBlockCell in foundBlock) {
if ((foundBlockCell.row == row) && (foundBlockCell.col == col)) { if ((foundBlockCell.row == row) && (foundBlockCell.col == col)) {
alreadyFound = true; alreadyFound = true;
} }
}); }
}); }
if (!alreadyFound) { if (!alreadyFound) {
final List<CellLocation> block = game.getSiblingCells(cellLocation, []); final List<CellLocation> block = game.getSiblingCells(cellLocation, []);
if (block.length >= 3) { if (block.length >= 3) {
...@@ -147,14 +180,14 @@ class Game { ...@@ -147,14 +180,14 @@ class Game {
} }
bool hasAtLeastOneAvailableBlock() { bool hasAtLeastOneAvailableBlock() {
final int boardSizeHorizontal = this.settings.boardSize; final int boardSizeHorizontal = gameSettings.boardSize;
final int boardSizeVertical = this.settings.boardSize; final int boardSizeVertical = gameSettings.boardSize;
for (var row = 0; row < boardSizeVertical; row++) { for (int row = 0; row < boardSizeVertical; row++) {
for (var col = 0; col < boardSizeHorizontal; col++) { for (int col = 0; col < boardSizeHorizontal; col++) {
final CellLocation cellLocation = CellLocation.go(row, col); final CellLocation cellLocation = CellLocation.go(row, col);
if (this.getCellValue(cellLocation) != null) { if (getCellValue(cellLocation) != null) {
final List<CellLocation> block = this.getSiblingCells(cellLocation, []); final List<CellLocation> block = getSiblingCells(cellLocation, []);
if (block.length >= 3) { if (block.length >= 3) {
// found one block => ok, not locked // found one block => ok, not locked
return true; return true;
...@@ -163,15 +196,15 @@ class Game { ...@@ -163,15 +196,15 @@ class Game {
} }
} }
print('Board is locked!'); printlog('Board is locked!');
return false; return false;
} }
bool isInBoard(CellLocation cell) { bool isInBoard(CellLocation cell) {
if (cell.row > 0 && if (cell.row > 0 &&
cell.row < this.settings.boardSize && cell.row < gameSettings.boardSize &&
cell.col > 0 && cell.col > 0 &&
cell.col < this.settings.boardSize) { cell.col < gameSettings.boardSize) {
return true; return true;
} }
return false; return false;
...@@ -184,30 +217,35 @@ class Game { ...@@ -184,30 +217,35 @@ class Game {
// build a list of values to pick one // build a list of values to pick one
final List<int> values = []; final List<int> values = [];
// All eligible values // All eligible values (twice)
final int maxValue = this.settings.colorsCount; final int maxValue = gameSettings.colorsCount;
for (int i = 1; i <= maxValue; i++) { for (int i = 1; i <= maxValue; i++) {
values.add(i); values.add(i);
values.add(i);
} }
// Add values of current col // Add values of current col (twice)
for (int r = 0; r <= this.settings.boardSize; r++) { for (int r = 0; r <= gameSettings.boardSize; r++) {
if (this.isInBoard(CellLocation.go(r, col))) { if (isInBoard(CellLocation.go(r, col))) {
final int? value = this.getCellValue(CellLocation.go(r, col)); final int? value = getCellValue(CellLocation.go(r, col));
if (value != null) { if (value != null) {
values.add(value); values.add(value);
values.add(value);
} }
} }
} }
// Add values of sibling cols // Add values of sibling cols (twice for top rows)
for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { for (int deltaCol = -1; deltaCol <= 1; deltaCol++) {
final int c = col + deltaCol; final int c = col + deltaCol;
for (int r = 0; r < this.settings.boardSize; r++) { for (int r = 0; r < gameSettings.boardSize; r++) {
if (this.isInBoard(CellLocation.go(r, c))) { if (isInBoard(CellLocation.go(r, c))) {
final int? value = this.getCellValue(CellLocation.go(r, c)); final int? value = getCellValue(CellLocation.go(r, c));
if (value != null) { if (value != null) {
values.add(value); values.add(value);
if (row < gameSettings.boardSize / 3) {
values.add(value);
}
} }
} }
} }
...@@ -218,8 +256,8 @@ class Game { ...@@ -218,8 +256,8 @@ class Game {
final int c = col + deltaCol; final int c = col + deltaCol;
for (int deltaRow = -2; deltaRow <= 2; deltaRow++) { for (int deltaRow = -2; deltaRow <= 2; deltaRow++) {
final int r = row + deltaRow; final int r = row + deltaRow;
if (this.isInBoard(CellLocation.go(r, c))) { if (isInBoard(CellLocation.go(r, c))) {
final int? value = this.getCellValue(CellLocation.go(r, c)); final int? value = getCellValue(CellLocation.go(r, c));
if (value != null) { if (value != null) {
values.add(value); values.add(value);
} }
...@@ -232,35 +270,40 @@ class Game { ...@@ -232,35 +270,40 @@ class Game {
} }
void dump() { void dump() {
print(''); printlog('');
print('## Current game dump:'); printlog('## Current game dump:');
print(''); printlog('');
this.settings.dump(); gameSettings.dump();
print(''); globalSettings.dump();
this.board.dump(); printlog('');
print(''); board.dump();
print('Game: '); printlog('');
print(' isRunning: ' + isRunning.toString()); printlog('Game: ');
print(' isFinished: ' + isFinished.toString()); printlog(' isRunning: $isRunning');
print(' movesCount: ' + movesCount.toString()); printlog(' isFinished: $isFinished');
print(' score: ' + score.toString()); printlog(' movesCount: $movesCount');
print(' availableBlocksCount: ' + availableBlocksCount.toString()); printlog(' score: $score');
print(''); printlog(' availableBlocksCount: $availableBlocksCount');
} printlog(' shuffledColors: $shuffledColors');
printlog('');
}
@override
String toString() { String toString() {
return 'Game(' + this.toJson().toString() + ')'; return 'Game(${toJson()})';
} }
Map<String, dynamic>? toJson() { Map<String, dynamic>? toJson() {
return <String, dynamic>{ return <String, dynamic>{
'board': this.board.toJson(), 'board': board.toJson(),
'settings': this.settings.toJson(), 'gameSettings': gameSettings.toJson(),
'isRunning': this.isRunning, 'globalSettings': globalSettings.toJson(),
'isFinished': this.isFinished, 'shuffledColors': shuffledColors,
'availableBlocksCount': this.availableBlocksCount, 'isRunning': isRunning,
'movesCount': this.movesCount, 'isFinished': isFinished,
'score': this.score, 'availableBlocksCount': availableBlocksCount,
'movesCount': movesCount,
'score': score,
}; };
} }
} }
import 'dart:math'; import 'dart:math';
import 'package:jeweled/models/game_cell.dart'; import 'package:jeweled/models/game_cell.dart';
import 'package:jeweled/models/game_settings.dart'; import 'package:jeweled/models/settings_game.dart';
import 'package:jeweled/utils/tools.dart';
class GameBoard { class GameBoard {
final List<List<GameCell>> cells; final List<List<GameCell>> cells;
...@@ -19,12 +20,12 @@ class GameBoard { ...@@ -19,12 +20,12 @@ class GameBoard {
final int boardSizeVertical = gameSettings.boardSize; final int boardSizeVertical = gameSettings.boardSize;
final int maxValue = gameSettings.colorsCount; final int maxValue = gameSettings.colorsCount;
final rand = new Random(); final rand = Random();
List<List<GameCell>> cells = []; List<List<GameCell>> cells = [];
for (var rowIndex = 0; rowIndex < boardSizeVertical; rowIndex++) { for (int rowIndex = 0; rowIndex < boardSizeVertical; rowIndex++) {
List<GameCell> row = []; List<GameCell> row = [];
for (var colIndex = 0; colIndex < boardSizeHorizontal; colIndex++) { for (int colIndex = 0; colIndex < boardSizeHorizontal; colIndex++) {
int value = 1 + rand.nextInt(maxValue); int value = 1 + rand.nextInt(maxValue);
row.add(GameCell(value)); row.add(GameCell(value));
} }
...@@ -38,33 +39,34 @@ class GameBoard { ...@@ -38,33 +39,34 @@ class GameBoard {
void dump() { void dump() {
String horizontalRule = '----'; String horizontalRule = '----';
cells[0].forEach((element) { for (int i = 0; i < cells[0].length; i++) {
horizontalRule += '-'; horizontalRule += '-';
}); }
print('Board:'); printlog('Board:');
print(horizontalRule); printlog(horizontalRule);
for (var rowIndex = 0; rowIndex < cells.length; rowIndex++) { for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
String row = '| '; String row = '| ';
for (var colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
row += cells[rowIndex][colIndex].value.toString(); row += cells[rowIndex][colIndex].value.toString();
} }
row += ' |'; row += ' |';
print(row); printlog(row);
} }
print(horizontalRule); printlog(horizontalRule);
} }
@override
String toString() { String toString() {
return 'Board(' + this.toJson().toString() + ')'; return 'Board(${toJson()})';
} }
Map<String, dynamic>? toJson() { Map<String, dynamic>? toJson() {
return <String, dynamic>{ return <String, dynamic>{
'cells': this.cells.toString(), 'cells': cells.toString(),
}; };
} }
} }
...@@ -5,13 +5,14 @@ class GameCell { ...@@ -5,13 +5,14 @@ class GameCell {
this.value, this.value,
); );
@override
String toString() { String toString() {
return 'Cell(' + this.toJson().toString() + ')'; return 'Cell(${toJson()})';
} }
Map<String, dynamic>? toJson() { Map<String, dynamic>? toJson() {
return <String, dynamic>{ return <String, dynamic>{
'value': this.value, 'value': value,
}; };
} }
} }
import 'package:jeweled/config/default_game_settings.dart'; import 'package:jeweled/config/default_game_settings.dart';
import 'package:jeweled/utils/tools.dart';
class GameSettings { class GameSettings {
final int boardSize; final int boardSize;
...@@ -33,19 +34,20 @@ class GameSettings { ...@@ -33,19 +34,20 @@ class GameSettings {
} }
void dump() { void dump() {
print('Settings: '); printlog('Settings: ');
print(' boardSize: ' + boardSize.toString()); printlog(' boardSize: $boardSize');
print(' colorsCount: ' + colorsCount.toString()); printlog(' colorsCount: $colorsCount');
} }
@override
String toString() { String toString() {
return 'GameSettings(' + this.toJson().toString() + ')'; return 'GameSettings(${toJson()})';
} }
Map<String, dynamic>? toJson() { Map<String, dynamic>? toJson() {
return <String, dynamic>{ return <String, dynamic>{
'boardSize': this.boardSize, 'boardSize': boardSize,
'colorsCount': this.colorsCount, 'colorsCount': colorsCount,
}; };
} }
} }
import 'package:jeweled/config/default_global_settings.dart';
import 'package:jeweled/utils/tools.dart';
class GlobalSettings {
final int colorsTheme;
final int graphicTheme;
GlobalSettings({
required this.colorsTheme,
required this.graphicTheme,
});
static int getColorsThemeValueFromUnsafe(int colorsTheme) {
if (DefaultGlobalSettings.allowedColorsThemeValues.contains(colorsTheme)) {
return colorsTheme;
}
return DefaultGlobalSettings.defaultColorsThemeValue;
}
static int getGraphicThemeValueFromUnsafe(int graphicTheme) {
if (DefaultGlobalSettings.allowedGraphicThemeValues.contains(graphicTheme)) {
return graphicTheme;
}
return DefaultGlobalSettings.defaultGraphicThemeValue;
}
factory GlobalSettings.createDefault() {
return GlobalSettings(
colorsTheme: DefaultGlobalSettings.defaultColorsThemeValue,
graphicTheme: DefaultGlobalSettings.defaultGraphicThemeValue,
);
}
void dump() {
printlog('Settings: ');
printlog(' colorsTheme: $colorsTheme');
printlog(' graphicTheme: $graphicTheme');
}
@override
String toString() {
return 'GlobalSettings(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'colorsTheme': colorsTheme,
'graphicTheme': graphicTheme,
};
}
}
import 'dart:math'; import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:jeweled/models/game_cell.dart'; import 'package:jeweled/config/default_global_settings.dart';
import 'package:jeweled/models/game.dart'; import 'package:jeweled/models/game.dart';
import 'package:jeweled/models/cell_location.dart'; import 'package:jeweled/models/cell_location.dart';
import 'package:jeweled/utils/color_extensions.dart';
import 'package:jeweled/utils/color_theme.dart'; import 'package:jeweled/utils/color_theme.dart';
class GameBoardPainter extends CustomPainter { class GameBoardPainter extends CustomPainter {
const GameBoardPainter(this.currentGame); const GameBoardPainter({
required this.game,
required this.animations,
});
final Game currentGame; final Game game;
final List<List<Animation<double>?>> animations;
static const drawTextValue = false;
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final int sizeHorizontal = currentGame.settings.boardSize; int graphicTheme = game.globalSettings.graphicTheme;
final int sizeVertical = currentGame.settings.boardSize;
final List cells = currentGame.board.cells; final double canvasSize = min(size.width, size.height);
final double cellSize = size.width / max(sizeHorizontal, sizeVertical);
// background const double cellBorderWidth = 2;
for (var row = 0; row < sizeVertical; row++) { const double boardBorderWidth = 3;
final double y = cellSize * row;
for (var col = 0; col < sizeHorizontal; col++) {
final double x = cellSize * col;
final GameCell cell = cells[row][col]; switch (graphicTheme) {
final int colorCode = ColorTheme.getColorCode(cell.value); case DefaultGlobalSettings.graphicThemeSolidBackground:
drawBoard(
canvas: canvas,
canvasSize: canvasSize,
game: game,
);
break;
case DefaultGlobalSettings.graphicThemeGradientAndBorder:
drawBoard(
canvas: canvas,
canvasSize: canvasSize,
game: game,
borderWidth: cellBorderWidth,
gradientFrom: -10,
gradientTo: 10,
);
break;
case DefaultGlobalSettings.graphicThemeEmojis:
drawBoard(
canvas: canvas,
canvasSize: canvasSize,
game: game,
contentStrings: DefaultGlobalSettings.graphicThemeContentEmojiStrings,
);
break;
case DefaultGlobalSettings.graphicThemePatterns:
drawBoard(
canvas: canvas,
canvasSize: canvasSize,
game: game,
contentStrings: DefaultGlobalSettings.graphicThemeContentPatternStrings,
);
break;
final cellPaintBackground = Paint(); default:
cellPaintBackground.color = Color(colorCode); }
cellPaintBackground.style = PaintingStyle.fill;
final Rect cellBackground = // board borders
Rect.fromPoints(Offset(x - 1, y - 1), Offset(x + cellSize + 2, y + cellSize + 2)); final boardPaintBorder = Paint();
boardPaintBorder.color = ColorTheme.getBorderColor();
boardPaintBorder.strokeWidth = boardBorderWidth;
boardPaintBorder.strokeCap = StrokeCap.round;
const Offset boardTopLeft = Offset(0, 0);
final Offset boardTopRight = Offset(canvasSize, 0);
final Offset boardBottomLeft = Offset(0, canvasSize);
final Offset boardBottomRight = Offset(canvasSize, canvasSize);
canvas.drawLine(boardTopLeft, boardTopRight, boardPaintBorder);
canvas.drawLine(boardTopRight, boardBottomRight, boardPaintBorder);
canvas.drawLine(boardBottomRight, boardBottomLeft, boardPaintBorder);
canvas.drawLine(boardBottomLeft, boardTopLeft, boardPaintBorder);
}
canvas.drawRect(cellBackground, cellPaintBackground); @override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
// draw value on cell void drawBoard({
if (drawTextValue) { required Canvas canvas,
final textPainter = TextPainter( required double canvasSize,
text: TextSpan( required Game game,
text: cell.value.toString(), double overlapping = 1,
style: const TextStyle( int gradientFrom = 0,
color: Colors.black, int gradientTo = 0,
fontSize: 15, double borderWidth = 0,
), List<String>? contentStrings,
), }) {
textDirection: TextDirection.ltr, final int cellsCountHorizontal = game.gameSettings.boardSize;
)..layout( final int cellsCountVertical = game.gameSettings.boardSize;
minWidth: 0, final int colorsTheme = game.globalSettings.colorsTheme;
maxWidth: size.width,
final double size = canvasSize / max(cellsCountHorizontal, cellsCountVertical);
for (int row = 0; row < cellsCountVertical; row++) {
final double yOrigin = size * row;
for (int col = 0; col < cellsCountHorizontal; col++) {
final double x = size * col;
final CellLocation cellLocation = CellLocation.go(row, col);
final int? cellValue = game.getCellValueShuffled(cellLocation);
if (cellValue != null) {
final Animation<double>? translation = animations[row][col];
final double y = yOrigin + (translation?.value ?? 0) * size;
drawCell(
canvas: canvas,
x: x,
y: y,
cellSize: size,
cellValue: cellValue,
colorsTheme: colorsTheme,
overlapping: overlapping,
gradientFrom: gradientFrom,
gradientTo: gradientTo,
borderWidth: borderWidth,
contentStrings: contentStrings,
); );
textPainter.paint(canvas, Offset(x + 4, y + 2));
} }
} }
} }
// borders
const double borderSize = 4;
final cellPaintBorder = Paint();
cellPaintBorder.color = ColorTheme.getBorderColor();
cellPaintBorder.strokeWidth = borderSize;
cellPaintBorder.strokeCap = StrokeCap.round;
for (var row = 0; row < sizeVertical; row++) {
final double y = cellSize * row;
for (var col = 0; col < sizeHorizontal; col++) {
final double x = cellSize * col;
final GameCell cell = cells[row][col];
final int? cellValue = cell.value;
// top border
if ((row == 0) ||
(row > 1 &&
cellValue != currentGame.getCellValue(CellLocation.go(row - 1, col)))) {
final Offset borderStart = Offset(x, y);
final Offset borderStop = Offset(x + cellSize, y);
canvas.drawLine(borderStart, borderStop, cellPaintBorder);
} }
// bottom border void drawCell({
if ((row == sizeVertical - 1) || required Canvas canvas,
((row + 1) < sizeVertical && required double x,
cellValue != currentGame.getCellValue(CellLocation.go(row + 1, col)))) { required double y,
final Offset borderStart = Offset(x, y + cellSize); required double cellSize,
final Offset borderStop = Offset(x + cellSize, y + cellSize); required int cellValue,
canvas.drawLine(borderStart, borderStop, cellPaintBorder); required int colorsTheme,
required double overlapping,
required int gradientFrom,
required int gradientTo,
required double borderWidth,
required List<String>? contentStrings,
}) {
final Color baseColor = ColorTheme.getColor(cellValue, colorsTheme);
final paint = Paint();
// draw background
final Rect rect = Rect.fromLTWH(x, y, cellSize + overlapping, cellSize + overlapping);
// solid or gradient
if (gradientFrom == 0 && gradientTo == 0) {
paint.color = baseColor;
} else {
paint.shader = ui.Gradient.linear(
rect.topLeft,
rect.bottomCenter,
[
(gradientFrom < 0)
? baseColor.lighten(-gradientFrom)
: baseColor.darken(gradientFrom),
(gradientTo < 0) ? baseColor.lighten(-gradientTo) : baseColor.darken(gradientTo),
],
);
} }
paint.style = PaintingStyle.fill;
canvas.drawRect(rect, paint);
// left border // draw border
if ((col == 0) || if (borderWidth != 0) {
(col > 1 && final Rect border = Rect.fromLTWH(x + borderWidth, y + borderWidth,
cellValue != currentGame.getCellValue(CellLocation.go(row, col - 1)))) { cellSize + overlapping - 2 * borderWidth, cellSize + overlapping - 2 * borderWidth);
final Offset borderStart = Offset(x, y);
final Offset borderStop = Offset(x, y + cellSize);
canvas.drawLine(borderStart, borderStop, cellPaintBorder);
}
// right border final paintBorder = Paint();
if ((col == sizeHorizontal - 1) || paintBorder.color = baseColor.darken(20);
((col + 1) < sizeHorizontal && paintBorder.style = PaintingStyle.stroke;
cellValue != currentGame.getCellValue(CellLocation.go(row, col + 1)))) {
final Offset borderStart = Offset(x + cellSize, y); paintBorder.strokeWidth = borderWidth;
final Offset borderStop = Offset(x + cellSize, y + cellSize); paintBorder.strokeJoin = StrokeJoin.round;
canvas.drawLine(borderStart, borderStop, cellPaintBorder); canvas.drawRect(border, paintBorder);
}
}
}
} }
@override // draw content
bool shouldRepaint(CustomPainter oldDelegate) { if (contentStrings != null) {
return false; final int emojiIndex =
cellValue - 1 + game.shuffledColors[0] + 2 * game.shuffledColors[1];
final String text = contentStrings[emojiIndex % contentStrings.length];
final textSpan = TextSpan(
text: text,
style: TextStyle(
color: Colors.black,
fontSize: 4 + cellSize / 2,
fontWeight: FontWeight.bold,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
x + (cellSize - textPainter.width) * 0.5,
y + (cellSize - textPainter.height) * 0.5,
),
);
}
} }
} }
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:jeweled/config/default_game_settings.dart';
import 'package:jeweled/config/default_global_settings.dart';
import 'package:jeweled/models/settings_game.dart';
import 'package:jeweled/models/settings_global.dart';
import 'package:jeweled/utils/color_extensions.dart';
import 'package:jeweled/utils/color_theme.dart';
import 'package:jeweled/utils/tools.dart';
class ParameterPainter extends CustomPainter {
const ParameterPainter({
required this.code,
required this.value,
required this.isSelected,
required this.gameSettings,
required this.globalSettings,
});
final String code;
final int value;
final bool isSelected;
final GameSettings gameSettings;
final GlobalSettings globalSettings;
@override
void paint(Canvas canvas, Size size) {
// force square
final double canvasSize = min(size.width, size.height);
const Color borderColorEnabled = Colors.blue;
const Color borderColorDisabled = Colors.white;
// "enabled/disabled" border
final paint = Paint();
paint.style = PaintingStyle.stroke;
paint.color = isSelected ? borderColorEnabled : borderColorDisabled;
paint.strokeJoin = StrokeJoin.round;
paint.strokeWidth = 20 / 100 * canvasSize;
canvas.drawRect(
Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint);
// content
switch (code) {
case 'colorsCount':
paintColorsCountParameterItem(value, canvas, canvasSize);
break;
case 'boardSize':
paintBoardSizeParameterItem(value, canvas, canvasSize);
break;
case 'colorsTheme':
paintColorsThemeParameterItem(value, canvas, canvasSize);
break;
case 'graphicTheme':
paintGraphicThemeParameterItem(value, canvas, canvasSize);
break;
default:
printlog('Unknown parameter: $code/$value');
paintUnknownParameterItem(value, canvas, canvasSize);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
// "unknown" parameter -> simple bock with text
void paintUnknownParameterItem(
final int value,
final Canvas canvas,
final double size,
) {
final paint = Paint();
paint.strokeJoin = StrokeJoin.round;
paint.strokeWidth = 3 / 100 * size;
paint.color = Colors.grey;
paint.style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);
final textSpan = TextSpan(
text: '?\n$value',
style: const TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
(size - textPainter.width) * 0.5,
(size - textPainter.height) * 0.5,
),
);
}
void paintBoardSizeParameterItem(
final int value,
final Canvas canvas,
final double size,
) {
Color backgroundColor = Colors.grey;
int gridWidth = 1;
switch (value) {
case DefaultGameSettings.boardSizeValueSmall:
backgroundColor = Colors.green;
gridWidth = 2;
break;
case DefaultGameSettings.boardSizeValueMedium:
backgroundColor = Colors.orange;
gridWidth = 3;
break;
case DefaultGameSettings.boardSizeValueLarge:
backgroundColor = Colors.red;
gridWidth = 4;
break;
case DefaultGameSettings.boardSizeValueExtraLarge:
backgroundColor = Colors.purple;
gridWidth = 5;
break;
default:
printlog('Wrong value for boardSize parameter value: $value');
}
final paint = Paint();
paint.strokeJoin = StrokeJoin.round;
paint.strokeWidth = 3 / 100 * size;
// Colored background
paint.color = backgroundColor;
paint.style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);
// Mini grid
final borderColor = Colors.grey.shade800;
final double cellSize = size / 7;
final double origin = (size - gridWidth * cellSize) / 2;
for (int row = 0; row < gridWidth; row++) {
for (int col = 0; col < gridWidth; col++) {
final Offset topLeft = Offset(origin + col * cellSize, origin + row * cellSize);
final Offset bottomRight = topLeft + Offset(cellSize, cellSize);
final squareColor =
Color(ColorTheme.getColorCode(col + row * gridWidth, globalSettings.colorsTheme));
paint.color = squareColor;
paint.style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint);
paint.color = borderColor;
paint.style = PaintingStyle.stroke;
canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint);
}
}
}
void paintColorsCountParameterItem(
final int value,
final Canvas canvas,
final double size,
) {
Color backgroundColor = Colors.grey;
switch (value) {
case DefaultGameSettings.colorsCountValueLow:
backgroundColor = Colors.green;
break;
case DefaultGameSettings.colorsCountValueMedium:
backgroundColor = Colors.orange;
break;
case DefaultGameSettings.colorsCountValueHigh:
backgroundColor = Colors.red;
break;
case DefaultGameSettings.colorsCountValueVeryHigh:
backgroundColor = Colors.purple;
break;
default:
printlog('Wrong value for colorsCount parameter value: $value');
}
final paint = Paint();
paint.strokeJoin = StrokeJoin.round;
paint.strokeWidth = 3 / 100 * size;
// Colored background
paint.color = backgroundColor;
paint.style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);
// Colors preview
const List<Offset> positions = [
Offset(0, 0),
Offset(1, 0),
Offset(2, 0),
Offset(2, 1),
Offset(2, 2),
Offset(1, 2),
Offset(0, 2),
Offset(0, 1),
];
final double padding = 4 / 100 * size;
final double margin = 3 / 100 * size;
final double width = ((size - 2 * padding) / 3) - 2 * margin;
for (int colorIndex = 0; colorIndex < value; colorIndex++) {
final Offset position = positions[colorIndex];
final Offset topLeft = Offset(padding + margin + position.dx * (width + 2 * margin),
padding + margin + position.dy * (width + 2 * margin));
final Offset bottomRight = topLeft + Offset(width, width);
final squareColor =
Color(ColorTheme.getColorCode(colorIndex, globalSettings.colorsTheme));
paint.color = squareColor;
paint.style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint);
final borderColor = squareColor.darken(20);
paint.color = borderColor;
paint.style = PaintingStyle.stroke;
canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint);
}
// centered text value
final textSpan = TextSpan(
text: value.toString(),
style: TextStyle(
color: Colors.black,
fontSize: size / 4,
fontWeight: FontWeight.bold,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
(size - textPainter.width) * 0.5,
(size - textPainter.height) * 0.5,
),
);
}
void paintColorsThemeParameterItem(
final int value,
final Canvas canvas,
final double size,
) {
Color backgroundColor = Colors.grey;
const int gridWidth = 4;
final paint = Paint();
paint.strokeJoin = StrokeJoin.round;
paint.strokeWidth = 3 / 100 * size;
// Colored background
paint.color = backgroundColor;
paint.style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);
// Mini grid
final borderColor = Colors.grey.shade800;
final double cellSize = size / gridWidth;
final double origin = (size - gridWidth * cellSize) / 2;
for (int row = 0; row < gridWidth; row++) {
for (int col = 0; col < gridWidth; col++) {
final Offset topLeft = Offset(origin + col * cellSize, origin + row * cellSize);
final Offset bottomRight = topLeft + Offset(cellSize, cellSize);
final squareColor = Color(ColorTheme.getColorCode(col + row * gridWidth, value));
paint.color = squareColor;
paint.style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint);
paint.color = borderColor;
paint.style = PaintingStyle.stroke;
canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint);
}
}
}
void paintGraphicThemeParameterItem(
final int value,
final Canvas canvas,
final double size,
) {
Color backgroundColor = Colors.grey;
final paint = Paint();
paint.strokeJoin = StrokeJoin.round;
paint.strokeWidth = 3 / 100 * size;
// Colored background
paint.color = backgroundColor;
paint.style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);
// cells preview
const List<Offset> positions = [
Offset(0, 0),
Offset(1, 0),
Offset(2, 0),
Offset(2, 1),
Offset(2, 2),
Offset(1, 2),
Offset(0, 2),
Offset(0, 1),
];
final double padding = 5 / 100 * size;
final double width = (size - 2 * padding) / 3;
bool drawBorder = false;
bool gradientColor = false;
List<String> contentStrings = [];
switch (value) {
case DefaultGlobalSettings.graphicThemeSolidBackground:
break;
case DefaultGlobalSettings.graphicThemeGradientAndBorder:
drawBorder = true;
gradientColor = true;
break;
case DefaultGlobalSettings.graphicThemeEmojis:
contentStrings = DefaultGlobalSettings.graphicThemeContentEmojiStrings;
break;
case DefaultGlobalSettings.graphicThemePatterns:
contentStrings = DefaultGlobalSettings.graphicThemeContentPatternStrings;
break;
default:
printlog('Wrong value for colorsCount parameter value: $value');
}
for (int itemValue = 0; itemValue < positions.length; itemValue++) {
final Offset position = positions[itemValue];
final Offset topLeft =
Offset(padding + position.dx * width, padding + position.dy * width);
final Offset bottomRight = topLeft + Offset(width, width);
final Rect itemBox = Rect.fromPoints(topLeft, bottomRight);
final itemColor = ColorTheme.getColor(itemValue, globalSettings.colorsTheme);
paint.color = itemColor;
paint.style = PaintingStyle.fill;
canvas.drawRect(itemBox, paint);
// gradient background
if (gradientColor) {
paint.shader = ui.Gradient.linear(itemBox.topLeft, itemBox.bottomCenter, [
itemColor.lighten(10),
itemColor.darken(10),
]);
paint.style = PaintingStyle.fill;
canvas.drawRect(itemBox, paint);
}
// cell border
if (drawBorder) {
final paintBorder = Paint();
paintBorder.color = itemColor.darken(20);
paintBorder.style = PaintingStyle.stroke;
paintBorder.strokeWidth = 2;
canvas.drawRect(itemBox, paintBorder);
}
// centered text value
if (contentStrings.isNotEmpty) {
final textSpan = TextSpan(
text: contentStrings[itemValue],
style: TextStyle(
color: Colors.black,
fontSize: width / 2,
fontWeight: FontWeight.bold,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
topLeft.dx + (width - textPainter.width) * 0.5,
topLeft.dy + (width - textPainter.height) * 0.5,
),
);
}
}
}
}
...@@ -9,9 +9,9 @@ class SkeletonScreen extends StatelessWidget { ...@@ -9,9 +9,9 @@ class SkeletonScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: GlobalAppBar(), appBar: const GlobalAppBar(),
extendBodyBehindAppBar: false, extendBodyBehindAppBar: false,
body: ScreenGame(), body: const ScreenGame(),
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,
); );
} }
......
...@@ -16,7 +16,9 @@ class GameWidget extends StatelessWidget { ...@@ -16,7 +16,9 @@ class GameWidget extends StatelessWidget {
builder: (BuildContext context, GameState gameState) { builder: (BuildContext context, GameState gameState) {
final Game currentGame = gameState.currentGame; final Game currentGame = gameState.currentGame;
return Column( return Container(
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
...@@ -25,8 +27,11 @@ class GameWidget extends StatelessWidget { ...@@ -25,8 +27,11 @@ class GameWidget extends StatelessWidget {
const SizedBox(height: 2), const SizedBox(height: 2),
const GameBoard(), const GameBoard(),
const SizedBox(height: 2), const SizedBox(height: 2),
currentGame.isFinished ? const GameBottomButtonsWidget() : const SizedBox.shrink(), currentGame.isFinished
? const GameBottomButtonsWidget()
: const SizedBox.shrink(),
], ],
),
); );
}, },
); );
......
...@@ -6,9 +6,95 @@ import 'package:jeweled/models/game.dart'; ...@@ -6,9 +6,95 @@ import 'package:jeweled/models/game.dart';
import 'package:jeweled/models/cell_location.dart'; import 'package:jeweled/models/cell_location.dart';
import 'package:jeweled/ui/painters/game_board_painter.dart'; import 'package:jeweled/ui/painters/game_board_painter.dart';
class GameBoard extends StatelessWidget { class GameBoard extends StatefulWidget {
const GameBoard({super.key}); const GameBoard({super.key});
@override
State<GameBoard> createState() => _GameBoard();
}
class _GameBoard extends State<GameBoard> with TickerProviderStateMixin {
final int animationDuration = 500;
List<List<Animation<double>?>> animations = [];
void resetAnimations(int boardSize) {
animations = List.generate(
boardSize,
(i) => List.generate(
boardSize,
(i) => null,
),
);
}
void animateCells(List<CellLocation> cellsToRemove) {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
final Game currentGame = gameCubit.state.currentGame;
// "move down" cells
final controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: animationDuration),
)..addListener(() {
if (mounted) {
setState(() {});
}
});
if (mounted) {
setState(() {});
}
Animation<double> animation(int count) => Tween(
begin: 0.0,
end: count.toDouble(),
).animate(CurvedAnimation(
curve: Curves.bounceOut,
parent: controller,
))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
gameCubit.postAnimate();
resetAnimations(currentGame.gameSettings.boardSize);
setState(() {});
controller.dispose();
}
});
// Count translation length for each cell to move
final List<List<int>> stepsDownCounts = List.generate(
currentGame.gameSettings.boardSize,
(i) => List.generate(
currentGame.gameSettings.boardSize,
(i) => 0,
),
);
for (CellLocation cellToRemove in cellsToRemove) {
for (int i = 0; i < cellToRemove.row; i++) {
stepsDownCounts[(cellToRemove.row - i) - 1][cellToRemove.col] += 1;
}
}
// Build animation for each cell to move
bool needAnimation = false;
for (CellLocation cellToRemove in cellsToRemove) {
for (int i = 0; i < cellToRemove.row; i++) {
final int stepsCount = stepsDownCounts[(cellToRemove.row - i) - 1][cellToRemove.col];
animations[(cellToRemove.row - i) - 1][cellToRemove.col] = animation(stepsCount);
needAnimation = true;
}
}
controller.forward().orCancel;
if (!needAnimation) {
gameCubit.postAnimate();
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double displayWidth = MediaQuery.of(context).size.width; final double displayWidth = MediaQuery.of(context).size.width;
...@@ -18,20 +104,38 @@ class GameBoard extends StatelessWidget { ...@@ -18,20 +104,38 @@ class GameBoard extends StatelessWidget {
builder: (BuildContext context, GameState gameState) { builder: (BuildContext context, GameState gameState) {
final Game currentGame = gameState.currentGame; final Game currentGame = gameState.currentGame;
if (animations.isEmpty) {
resetAnimations(currentGame.gameSettings.boardSize);
}
return GestureDetector( return GestureDetector(
onTapUp: (details) { onTapUp: (details) {
double xTap = details.localPosition.dx; bool animationInProgress = false;
double yTap = details.localPosition.dy; for (List<Animation<double>?> row in animations) {
int col = xTap ~/ (displayWidth / currentGame.settings.boardSize); for (Animation<double>? cell in row) {
int row = yTap ~/ (displayWidth / currentGame.settings.boardSize); if (cell != null) {
animationInProgress = true;
}
}
}
if (!animationInProgress) {
final double xTap = details.localPosition.dx;
final double yTap = details.localPosition.dy;
final int col = xTap ~/ (displayWidth / currentGame.gameSettings.boardSize);
final int row = yTap ~/ (displayWidth / currentGame.gameSettings.boardSize);
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
gameCubit.tapOnCell(CellLocation.go(row, col)); animateCells(gameCubit.tapOnCell(CellLocation.go(row, col)));
}
}, },
child: CustomPaint( child: CustomPaint(
size: Size(displayWidth, displayWidth), size: Size(displayWidth, displayWidth),
willChange: false, willChange: false,
painter: GameBoardPainter(currentGame), painter: GameBoardPainter(
game: currentGame,
animations: animations,
),
isComplex: true, isComplex: true,
), ),
); );
......
...@@ -15,12 +15,11 @@ class GameBottomButtonsWidget extends StatelessWidget { ...@@ -15,12 +15,11 @@ class GameBottomButtonsWidget extends StatelessWidget {
image: AssetImage(decorationImageAssetName), image: AssetImage(decorationImageAssetName),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
onPressed: () => null, onPressed: () {},
); );
return Container( return Table(
child: Table( defaultColumnWidth: const IntrinsicColumnWidth(),
defaultColumnWidth: IntrinsicColumnWidth(),
children: [ children: [
TableRow( TableRow(
children: [ children: [
...@@ -30,7 +29,7 @@ class GameBottomButtonsWidget extends StatelessWidget { ...@@ -30,7 +29,7 @@ class GameBottomButtonsWidget extends StatelessWidget {
Column( Column(
children: [ children: [
TextButton( TextButton(
child: Image( child: const Image(
image: AssetImage('assets/icons/button_back.png'), image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
...@@ -47,7 +46,6 @@ class GameBottomButtonsWidget extends StatelessWidget { ...@@ -47,7 +46,6 @@ class GameBottomButtonsWidget extends StatelessWidget {
], ],
), ),
], ],
),
); );
} }
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:unicons/unicons.dart';
import 'package:jeweled/cubit/game_cubit.dart'; import 'package:jeweled/cubit/game_cubit.dart';
import 'package:jeweled/models/game.dart'; import 'package:jeweled/models/game.dart';
...@@ -55,6 +56,13 @@ class GameTopIndicatorWidget extends StatelessWidget { ...@@ -55,6 +56,13 @@ class GameTopIndicatorWidget extends StatelessWidget {
color: availableBlocksCountTextColor, color: availableBlocksCountTextColor,
), ),
), ),
TextButton(
child: const Icon(UniconsSolid.refresh),
onPressed: () {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
gameCubit.shuffleColors(currentGame.globalSettings.colorsTheme);
},
)
], ],
), ),
], ],
......
...@@ -18,13 +18,11 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -18,13 +18,11 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
if (currentGame.isRunning) { if (currentGame.isRunning) {
menuActions.add(TextButton( menuActions.add(TextButton(
child: Container( child: const Image(
child: Image(
image: AssetImage('assets/icons/button_back.png'), image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
), onPressed: () {},
onPressed: () => null,
onLongPress: () { onLongPress: () {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
gameCubit.quitGame(); gameCubit.quitGame();
......
...@@ -2,97 +2,120 @@ import 'package:flutter/material.dart'; ...@@ -2,97 +2,120 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jeweled/config/default_game_settings.dart'; import 'package:jeweled/config/default_game_settings.dart';
import 'package:jeweled/config/default_global_settings.dart';
import 'package:jeweled/cubit/game_cubit.dart'; import 'package:jeweled/cubit/game_cubit.dart';
import 'package:jeweled/cubit/settings_cubit.dart'; import 'package:jeweled/cubit/settings_game_cubit.dart';
import 'package:jeweled/models/game_settings.dart'; import 'package:jeweled/cubit/settings_global_cubit.dart';
import 'package:jeweled/ui/painters/parameter_painter.dart';
class Parameters extends StatelessWidget { class Parameters extends StatelessWidget {
const Parameters({super.key}); const Parameters({super.key});
static const double separatorHeight = 2.0; final double separatorHeight = 8.0;
static const double blockMargin = 3.0;
static const double blockPadding = 2.0;
static const Color buttonBackgroundColor = Colors.white;
static const Color buttonBorderColorActive = Colors.blue;
static const Color buttonBorderColorInactive = Colors.white;
static const double buttonBorderWidth = 10.0;
static const double buttonBorderRadius = 8.0;
static const double buttonPadding = 0.0;
static const double buttonMargin = 0.0;
@override List<Widget> buildParametersLine({
Widget build(BuildContext context) { required String code,
return BlocBuilder<SettingsCubit, SettingsState>( required bool isGlobal,
builder: (BuildContext context, SettingsState settingsState) { }) {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); final List<Widget> parameterButtons = [];
final SettingsCubit settingsCubit = BlocProvider.of<SettingsCubit>(context);
final List<Widget> lines = [];
DefaultGameSettings.availableParameters.forEach((code) { final List<int> availableValues = isGlobal
final List<dynamic> availableValues = DefaultGameSettings.getAvailableValues(code); ? DefaultGlobalSettings.getAvailableValues(code)
: DefaultGameSettings.getAvailableValues(code);
if (availableValues.length > 1) { for (int value in availableValues) {
final List<Widget> parameterButtons = []; final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>(
builder: (BuildContext context, GameSettingsState gameSettingsState) {
return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>(
builder: (BuildContext context, GlobalSettingsState globalSettingsState) {
final GameSettingsCubit gameSettingsCubit =
BlocProvider.of<GameSettingsCubit>(context);
final GlobalSettingsCubit globalSettingsCubit =
BlocProvider.of<GlobalSettingsCubit>(context);
final dynamic currentValue = settingsCubit.getParameterValue(code); final int currentValue = isGlobal
? globalSettingsCubit.getParameterValue(code)
: gameSettingsCubit.getParameterValue(code);
availableValues.forEach((value) {
final bool isActive = (value == currentValue); final bool isActive = (value == currentValue);
final String imageAsset = code + '_' + value.toString();
final Widget parameterButton = TextButton( final double displayWidth = MediaQuery.of(context).size.width;
final double itemWidth = displayWidth / availableValues.length - 25;
return TextButton(
child: Container( child: Container(
margin: EdgeInsets.all(buttonMargin), margin: const EdgeInsets.all(0),
padding: EdgeInsets.all(buttonPadding), padding: const EdgeInsets.all(0),
decoration: BoxDecoration( child: CustomPaint(
color: buttonBackgroundColor, size: Size(itemWidth, itemWidth),
borderRadius: BorderRadius.circular(buttonBorderRadius), willChange: false,
border: Border.all( painter: ParameterPainter(
color: isActive ? buttonBorderColorActive : buttonBorderColorInactive, code: code,
width: buttonBorderWidth, value: value,
isSelected: isActive,
gameSettings: gameSettingsState.settings,
globalSettings: globalSettingsState.settings,
), ),
isComplex: true,
), ),
child: buildImageWidget(imageAsset),
), ),
onPressed: () => settingsCubit.setParameterValue(code, value), onPressed: () => isGlobal
? globalSettingsCubit.setParameterValue(code, value)
: gameSettingsCubit.setParameterValue(code, value),
);
},
);
},
); );
parameterButtons.add(parameterButton); parameterButtons.add(parameterButton);
}); }
lines.add(Table( return parameterButtons;
defaultColumnWidth: IntrinsicColumnWidth(), }
children: [
TableRow( @override
children: parameterButtons, Widget build(BuildContext context) {
final List<Widget> lines = [];
// Game settings
for (String code in DefaultGameSettings.availableParameters) {
lines.add(Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: buildParametersLine(
code: code,
isGlobal: false,
), ),
],
)); ));
lines.add(SizedBox(height: separatorHeight)); lines.add(SizedBox(height: separatorHeight));
} }
});
return Container( lines.add(SizedBox(height: separatorHeight));
child: Column( lines.add(buildStartNewGameButton());
children: [ lines.add(SizedBox(height: separatorHeight));
SizedBox(height: separatorHeight),
Column( // Global settings
children: lines, for (String code in DefaultGlobalSettings.availableParameters) {
), lines.add(Row(
SizedBox(height: separatorHeight), mainAxisAlignment: MainAxisAlignment.spaceBetween,
buildStartNewGameButton(gameCubit, settingsState.settings), children: buildParametersLine(
], code: code,
isGlobal: true,
), ),
); ));
},
lines.add(SizedBox(height: separatorHeight));
}
return Column(
children: lines,
); );
} }
static Image buildImageWidget(String imageAssetCode) { static Image buildImageWidget(String imageAssetCode) {
return Image( return Image(
image: AssetImage('assets/icons/' + imageAssetCode + '.png'), image: AssetImage('assets/icons/$imageAssetCode.png'),
fit: BoxFit.fill, fit: BoxFit.fill,
); );
} }
...@@ -108,18 +131,27 @@ class Parameters extends StatelessWidget { ...@@ -108,18 +131,27 @@ class Parameters extends StatelessWidget {
children: [ children: [
TextButton( TextButton(
child: buildImageContainerWidget('placeholder'), child: buildImageContainerWidget('placeholder'),
onPressed: () => null, onPressed: () {},
), ),
], ],
); );
} }
static Container buildStartNewGameButton(GameCubit gameCubit, GameSettings settings) { static Widget buildStartNewGameButton() {
const double blockMargin = 3.0;
const double blockPadding = 2.0;
return BlocBuilder<GameSettingsCubit, GameSettingsState>(
builder: (BuildContext context, GameSettingsState gameSettingsState) {
return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>(
builder: (BuildContext context, GlobalSettingsState globalSettingsState) {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
return Container( return Container(
margin: EdgeInsets.all(blockMargin), margin: const EdgeInsets.all(blockMargin),
padding: EdgeInsets.all(blockPadding), padding: const EdgeInsets.all(blockPadding),
child: Table( child: Table(
defaultColumnWidth: IntrinsicColumnWidth(), defaultColumnWidth: const IntrinsicColumnWidth(),
children: [ children: [
TableRow( TableRow(
children: [ children: [
...@@ -128,7 +160,10 @@ class Parameters extends StatelessWidget { ...@@ -128,7 +160,10 @@ class Parameters extends StatelessWidget {
children: [ children: [
TextButton( TextButton(
child: buildImageContainerWidget('button_start'), child: buildImageContainerWidget('button_start'),
onPressed: () => gameCubit.startNewGame(settings), onPressed: () => gameCubit.startNewGame(
gameSettings: gameSettingsState.settings,
globalSettings: globalSettingsState.settings,
),
), ),
], ],
), ),
...@@ -138,5 +173,9 @@ class Parameters extends StatelessWidget { ...@@ -138,5 +173,9 @@ class Parameters extends StatelessWidget {
], ],
), ),
); );
},
);
},
);
} }
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ColorTheme { class ColorTheme {
static Map<String, List<int>> itemColors = { static Map<int, List<int>> itemColors = {
'default': [ // legacy
0xffffff, // 0:[0x9D9D9D,0xFFFFFF,0xBE2633,0xE06F8B,0x493C2B,0xA46422,0xEB8931,0xF7E26B,0x2F484E,0x44891A,0xA3CE27,0x1B2632,0x005784,0x31A2F2,0xB2DCEF,],
0xe63a3f,
0x708cfd, // https://lospec.com/palette-list/gothic-bit
0x359c35, 1: [
0xffce2c, 0x0e0e12,
0xff6f43, 0x1a1a24,
0xa13cb1, 0x333346,
0x38ffff, 0x535373,
0xf2739d, 0x8080a4,
0xa6a6bf,
0xc1c1d2,
0xe6e6ec,
],
// https://lospec.com/palette-list/sweethope
2: [
0x615e85,
0x9c8dc2,
0xd9a3cd,
0xebc3a7,
0xe0e0dc,
0xa3d1af,
0x90b4de,
0x717fb0,
], ],
// https://lospec.com/palette-list/nostalgic-dreams
3: [
0xd9af80,
0xb07972,
0x524352,
0x686887,
0x7f9bb0,
0xbfd4b0,
0x90b870,
0x628c70,
],
// https://lospec.com/palette-list/arjibi8
4: [
0x8bc7bf,
0x5796a1,
0x524bb3,
0x471b6e,
0x702782,
0xb0455a,
0xde8b6f,
0xebd694,
],
// https://lospec.com/palette-list/kotomasho-8
// 5:[0x40263e,0x5979a6,0x84c2a3,0xefe8c3,0xefefef,0xcbc7d6,0xd06060,0x773971,],
// https://lospec.com/palette-list/desatur8
// 6:[0xf0f0eb,0xffff8f,0x7be098,0x849ad8,0xe8b382,0xd8828e,0xa776c1,0x545155,],
// https://lospec.com/palette-list/purplemorning8
// 7:[0x211d38,0x2e2a4f,0x3b405e,0x60556e,0x9a6278,0xc7786f,0xcfa98a,0xcdd4a5,],
// https://lospec.com/palette-list/cold-war-8
// 8:[0x332422,0xc95b40,0xff9b5e,0xfcdf76,0x4c2f7f,0x3a66ad,0x39cec2,0xfafff9,],
// https://lospec.com/palette-list/low-8
// 9:[0x111323,0x374566,0x50785d,0x8497b3,0xe8dcd8,0xcfb463,0xb35447,0x692e4b,],
}; };
static int defaultItemColor = 0x808080; static int defaultItemColor = 0x808080;
static int getColorCode(int? value) { static int getColorsCount(final int skin) {
const skin = 'default'; if (itemColors.containsKey(skin) && null != itemColors[skin]) {
List<int> skinColors = itemColors[skin] ?? [];
return skinColors.length;
}
return 0;
}
static int getColorCode(int? value, final int skin) {
if (value != null && itemColors.containsKey(skin) && null != itemColors[skin]) { if (value != null && itemColors.containsKey(skin) && null != itemColors[skin]) {
List<int> skinColors = itemColors[skin] ?? []; List<int> skinColors = itemColors[skin] ?? [];
if (skinColors.length > value) { return (skinColors[value % getColorsCount(skin)]) | 0xFF000000;
return (skinColors[value]) | 0xFF000000;
}
} }
return defaultItemColor | 0xFF000000; return defaultItemColor | 0xFF000000;
} }
static Color getColor(int? value, final int skin) {
return Color(getColorCode(value, skin));
}
static Color getBorderColor() { static Color getBorderColor() {
return Colors.black; return Colors.grey;
} }
} }
import 'package:flutter/foundation.dart';
void printlog(String message) {
if (!kReleaseMode) {
debugPrint(message);
}
}