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
  • 56-upgrade-framework-and-dependencies
  • 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
53 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
  • 56-upgrade-framework-and-dependencies
  • 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
53 results
Show changes
Showing
with 1084 additions and 850 deletions
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#F2739D" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="2.6"/></svg>
class DefaultGameSettings {
static const List<String> availableParameters = [
'boardSize',
'colorsCount',
];
static const int defaultBoardSizeValue = 10;
static const List<int> allowedBoardSizeValues = [
6,
10,
14,
20,
];
static const int defaultColorsCountValue = 6;
static const List<int> allowedColorsCountValues = [
5,
6,
7,
8,
];
static List<int> getAvailableValues(String parameterCode) {
switch (parameterCode) {
case 'boardSize':
return DefaultGameSettings.allowedBoardSizeValues;
case 'colorsCount':
return DefaultGameSettings.allowedColorsCountValues;
}
return [];
}
}
import 'package:flutter/material.dart';
/// Colors from Tailwind CSS (v3.0) - June 2022
///
/// https://tailwindcss.com/docs/customizing-colors
const int _primaryColor = 0xFF6366F1;
const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{
50: Color(0xFFEEF2FF), // indigo-50
100: Color(0xFFE0E7FF), // indigo-100
200: Color(0xFFC7D2FE), // indigo-200
300: Color(0xFFA5B4FC), // indigo-300
400: Color(0xFF818CF8), // indigo-400
500: Color(_primaryColor), // indigo-500
600: Color(0xFF4F46E5), // indigo-600
700: Color(0xFF4338CA), // indigo-700
800: Color(0xFF3730A3), // indigo-800
900: Color(0xFF312E81), // indigo-900
});
const int _textColor = 0xFF64748B;
const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{
50: Color(0xFFF8FAFC), // slate-50
100: Color(0xFFF1F5F9), // slate-100
200: Color(0xFFE2E8F0), // slate-200
300: Color(0xFFCBD5E1), // slate-300
400: Color(0xFF94A3B8), // slate-400
500: Color(_textColor), // slate-500
600: Color(0xFF475569), // slate-600
700: Color(0xFF334155), // slate-700
800: Color(0xFF1E293B), // slate-800
900: Color(0xFF0F172A), // slate-900
});
const Color errorColor = Color(0xFFDC2626); // red-600
final ColorScheme lightColorScheme = ColorScheme.light(
primary: primarySwatch.shade500,
secondary: primarySwatch.shade500,
onSecondary: Colors.white,
error: errorColor,
background: textSwatch.shade200,
onBackground: textSwatch.shade500,
onSurface: textSwatch.shade500,
surface: textSwatch.shade50,
surfaceVariant: Colors.white,
shadow: textSwatch.shade900.withOpacity(.1),
);
final ColorScheme darkColorScheme = ColorScheme.dark(
primary: primarySwatch.shade500,
secondary: primarySwatch.shade500,
onSecondary: Colors.white,
error: errorColor,
background: const Color(0xFF171724),
onBackground: textSwatch.shade400,
onSurface: textSwatch.shade300,
surface: const Color(0xFF262630),
surfaceVariant: const Color(0xFF282832),
shadow: textSwatch.shade900.withOpacity(.2),
);
final ThemeData lightTheme = ThemeData(
colorScheme: lightColorScheme,
fontFamily: 'Nunito',
textTheme: TextTheme(
displayLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
displayMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
displaySmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
headlineLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
headlineMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
headlineSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
titleLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
titleMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
titleSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
bodyLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
bodyMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
bodySmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
labelLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
labelMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
labelSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
),
);
final ThemeData darkTheme = lightTheme.copyWith(
colorScheme: darkColorScheme,
textTheme: TextTheme(
displayLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
displayMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
displaySmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
headlineLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
headlineMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
headlineSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
titleLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
titleMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
titleSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
bodyLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
bodyMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
bodySmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
labelLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
labelMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
labelSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
),
);
final ThemeData appTheme = darkTheme;
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:jeweled/models/game.dart';
import 'package:jeweled/models/cell_location.dart';
import 'package:jeweled/models/game_settings.dart';
part 'game_state.dart';
class GameCubit extends HydratedCubit<GameState> {
GameCubit()
: super(GameState(
currentGame: Game.createNull(),
));
void updateState(Game game) {
emit(GameState(
currentGame: game,
));
}
void refresh() {
final Game game = Game(
board: state.currentGame.board,
settings: state.currentGame.settings,
isRunning: state.currentGame.isRunning,
isFinished: state.currentGame.isFinished,
availableBlocksCount: state.currentGame.availableBlocksCount,
movesCount: state.currentGame.movesCount,
score: state.currentGame.score,
);
// game.dump();
updateState(game);
}
void quitGame() {
state.currentGame.updateGameIsRunning(false);
refresh();
}
void updateCellValue(CellLocation locationToUpdate, int? value) {
state.currentGame.updateCellValue(locationToUpdate, value);
refresh();
}
void increaseMovesCount() {
this.state.currentGame.increaseMovesCount();
refresh();
}
void increaseScore(int increment) {
this.state.currentGame.increaseScore(increment);
refresh();
}
void updateAvailableBlocksCount() {
this.state.currentGame.updateAvailableBlocksCount();
refresh();
}
void updateGameIsFinished(bool gameIsFinished) {
this.state.currentGame.updateGameIsFinished(gameIsFinished);
refresh();
}
moveCellsDown() {
final Game currentGame = this.state.currentGame;
final int boardSizeHorizontal = currentGame.settings.boardSize;
final int boardSizeVertical = currentGame.settings.boardSize;
for (int row = 0; row < boardSizeVertical; row++) {
for (int col = 0; col < boardSizeHorizontal; col++) {
// empty cell?
if (currentGame.getCellValue(CellLocation.go(row, col)) == null) {
// move cells down
for (int r = row; r > 0; r--) {
this.updateCellValue(CellLocation.go(r, col),
currentGame.getCellValue(CellLocation.go(r - 1, col)));
}
// fill top empty cell
this.updateCellValue(
CellLocation.go(0, col), currentGame.getFillValue(CellLocation.go(row, col)));
}
}
}
}
void deleteBlock(List<CellLocation> block) {
// Sort cells from top to bottom
block.sort((cell1, cell2) => cell1.row.compareTo(cell2.row));
// Delete all cells
block.forEach((CellLocation blockItemToDelete) {
this.updateCellValue(blockItemToDelete, null);
});
// Gravity!
this.moveCellsDown();
}
int getScoreFromBlock(int blockSize) {
return 3 * (blockSize - 2);
}
void tapOnCell(CellLocation tappedCellLocation) {
final Game currentGame = this.state.currentGame;
final int? cellValue = currentGame.getCellValue(tappedCellLocation);
print('Tap on cell: col=' +
tappedCellLocation.col.toString() +
' ; row=' +
tappedCellLocation.row.toString() +
' ; value=' +
cellValue.toString());
if (cellValue != null) {
List<CellLocation> block = currentGame.getSiblingCells(tappedCellLocation, []);
print('block size: ' + block.length.toString());
if (block.length >= 3) {
this.deleteBlock(block);
this.increaseMovesCount();
this.increaseScore(getScoreFromBlock(block.length));
this.updateAvailableBlocksCount();
}
}
if (!currentGame.hasAtLeastOneAvailableBlock()) {
print('no more block found. finish game.');
this.updateGameIsFinished(true);
}
}
void startNewGame(GameSettings settings) {
Game newGame = Game.createNew(
gameSettings: settings,
);
newGame.dump();
updateState(newGame);
}
@override
GameState? fromJson(Map<String, dynamic> json) {
Game currentGame = json['currentGame'] as Game;
return GameState(
currentGame: currentGame,
);
}
@override
Map<String, dynamic>? toJson(GameState state) {
return <String, dynamic>{
'currentGame': state.currentGame.toJson(),
};
}
}
part of 'game_cubit.dart';
@immutable
class GameState extends Equatable {
const GameState({
required this.currentGame,
});
final Game currentGame;
@override
List<dynamic> get props => <dynamic>[
currentGame,
];
Map<String, dynamic> get values => <String, dynamic>{
'currentGame': currentGame,
};
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:jeweled/models/game_settings.dart';
part 'settings_state.dart';
class SettingsCubit extends HydratedCubit<SettingsState> {
SettingsCubit() : super(SettingsState(settings: GameSettings.createDefault()));
void setValues({
int? boardSize,
int? colorsCount,
}) {
emit(
SettingsState(
settings: GameSettings(
boardSize: boardSize ?? state.settings.boardSize,
colorsCount: colorsCount ?? state.settings.colorsCount,
),
),
);
}
int getParameterValue(String code) {
switch (code) {
case 'boardSize':
return GameSettings.getBoardSizeValueFromUnsafe(state.settings.boardSize);
case 'colorsCount':
return GameSettings.getColorsCountValueFromUnsafe(state.settings.colorsCount);
}
return 0;
}
void setParameterValue(String code, int value) {
print('SettingsCubit.setParameterValue');
print('code: ' + code + ' / value: ' + value.toString());
int boardSize = code == 'boardSize' ? value : getParameterValue('boardSize');
int colorsCount = code == 'colorsCount' ? value : getParameterValue('colorsCount');
setValues(
boardSize: boardSize,
colorsCount: colorsCount,
);
}
@override
SettingsState? fromJson(Map<String, dynamic> json) {
int boardSize = json['boardSize'] as int;
int colorsCount = json['colorsCount'] as int;
return SettingsState(
settings: GameSettings(
boardSize: boardSize,
colorsCount: colorsCount,
),
);
}
@override
Map<String, dynamic>? toJson(SettingsState state) {
return <String, dynamic>{
'boardSize': state.settings.boardSize,
'colorsCount': state.settings.colorsCount,
};
}
}
part of 'settings_cubit.dart';
@immutable
class SettingsState extends Equatable {
const SettingsState({
required this.settings,
});
final GameSettings settings;
@override
List<dynamic> get props => <dynamic>[
settings,
];
Map<String, dynamic> get values => <String, dynamic>{
'settings': settings,
};
}
import 'package:flutter/material.dart';
import 'package:jeweled_game/provider/data.dart';
import 'package:jeweled_game/utils/board_utils.dart';
class Cell {
String value = '0';
Cell(
this.value,
);
/*
* Build widget for board cell, with interactions
* @TODO: remove parameters
*/
Container widget(Data myProvider, int row, int col) {
String imageAsset = this.getImageAssetName(myProvider);
return Container(
child: GestureDetector(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(child: child, scale: animation);
},
child: Image(
image: AssetImage(imageAsset),
fit: BoxFit.fill,
key: ValueKey<int>(imageAsset.hashCode),
),
),
onTap: () {
if (!myProvider.isGameFinished) {
BoardUtils.tapOnCell(myProvider, row, col);
}
},
),
);
}
/*
* Compute image asset name, from skin and cell value/state
*/
String getImageAssetName(Data myProvider) {
String imageAsset = 'assets/skins/' + myProvider.parameterSkin + '_' + this.value + '.png';
return imageAsset;
}
}
import 'package:flutter/material.dart';
import 'package:jeweled_game/provider/data.dart';
class Board {
static Container buildGameBoard(Data myProvider) {
return Container(
margin: EdgeInsets.all(4),
padding: EdgeInsets.all(4),
child: Column(
children: [
buildGameTileset(myProvider),
],
),
);
}
static Table buildGameTileset(Data myProvider) {
int boardSizeHorizontal = myProvider.sizeHorizontal;
int boardSizeVertical = myProvider.sizeVertical;
return Table(
defaultColumnWidth: IntrinsicColumnWidth(),
children: [
for (var row = 0; row < boardSizeVertical; row++)
TableRow(
children: [
for (var col = 0; col < boardSizeHorizontal; col++)
Column(
children: [
myProvider.getCell(row, col).widget(myProvider, row, col),
],
),
],
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:jeweled_game/layout/board.dart';
import 'package:jeweled_game/provider/data.dart';
import 'package:jeweled_game/utils/game_utils.dart';
class Game {
static Container buildGameWidget(Data myProvider) {
bool gameIsFinished = myProvider.isGameFinished;
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 8),
Game.buildTopIndicatorWidget(myProvider),
SizedBox(height: 2),
Expanded(
child: Board.buildGameBoard(myProvider),
),
SizedBox(height: 2),
Container(
child: gameIsFinished ? Game.buildEndGameMessage(myProvider) : SizedBox(height: 2),
),
],
),
);
}
static Widget buildTopIndicatorWidget(Data myProvider) {
return Table(
children: [
TableRow(
children: [
Column(
children: [
Text(
myProvider.score.toString(),
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Text(
myProvider.movesCount.toString(),
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Colors.grey,
),
),
],
),
Column(
children: [
Text(
myProvider.availableBlocksCount.toString(),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.green,
),
),
],
),
],
),
],
);
}
static TextButton buildQuitGameButton(Data myProvider) {
return TextButton(
child: Image(
image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill,
),
onPressed: () => GameUtils.quitGame(myProvider),
);
}
static Container buildEndGameMessage(Data myProvider) {
String decorationImageAssetName = 'assets/icons/game_fail.png';
Widget decorationWidget = TextButton(
child: Image(
image: AssetImage(decorationImageAssetName),
fit: BoxFit.fill,
),
onPressed: () => null,
);
return Container(
margin: EdgeInsets.all(2),
padding: EdgeInsets.all(2),
child: Table(
defaultColumnWidth: IntrinsicColumnWidth(),
children: [
TableRow(
children: [
Column(
children: [decorationWidget],
),
Column(
children: [buildQuitGameButton(myProvider)],
),
Column(
children: [decorationWidget],
),
],
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:jeweled_game/provider/data.dart';
import 'package:jeweled_game/utils/game_utils.dart';
class Parameters {
static double separatorHeight = 2.0;
static double blockMargin = 3.0;
static double blockPadding = 2.0;
static Color buttonBackgroundColor = Colors.white;
static Color buttonBorderColorActive = Colors.blue;
static Color buttonBorderColorInactive = Colors.white;
static double buttonBorderWidth = 10.0;
static double buttonBorderRadius = 8.0;
static double buttonPadding = 0.0;
static double buttonMargin = 0.0;
static Container buildParametersSelector(Data myProvider) {
List<Widget> lines = [];
List parameters = myProvider.availableParameters;
for (var index = 0; index < parameters.length; index++) {
lines.add(buildParameterSelector(myProvider, parameters[index]));
lines.add(SizedBox(height: separatorHeight));
}
myProvider.loadCurrentSavedState();
Widget buttonsBlock = myProvider.hasCurrentSavedState()
? buildResumeGameButton(myProvider)
: buildStartNewGameButton(myProvider);
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: separatorHeight),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: lines,
),
),
SizedBox(height: separatorHeight),
Container(
child: buttonsBlock,
),
],
),
);
}
static Image buildImageWidget(String imageAssetCode) {
return Image(
image: AssetImage('assets/icons/' + imageAssetCode + '.png'),
fit: BoxFit.fill,
);
}
static Container buildImageContainerWidget(String imageAssetCode) {
return Container(
child: buildImageWidget(imageAssetCode),
);
}
static Column buildDecorationImageWidget() {
return Column(
children: [
TextButton(
child: buildImageContainerWidget('placeholder'),
onPressed: () => null,
),
],
);
}
static Container buildStartNewGameButton(Data myProvider) {
return Container(
margin: EdgeInsets.all(blockMargin),
padding: EdgeInsets.all(blockPadding),
child: Table(
defaultColumnWidth: IntrinsicColumnWidth(),
children: [
TableRow(
children: [
buildDecorationImageWidget(),
Column(
children: [
TextButton(
child: buildImageContainerWidget('button_start'),
onPressed: () => GameUtils.startNewGame(myProvider),
),
],
),
buildDecorationImageWidget(),
],
),
],
),
);
}
static Container buildResumeGameButton(Data myProvider) {
return Container(
margin: EdgeInsets.all(blockMargin),
padding: EdgeInsets.all(blockPadding),
child: Table(
defaultColumnWidth: IntrinsicColumnWidth(),
children: [
TableRow(
children: [
Column(
children: [
TextButton(
child: buildImageContainerWidget('button_delete_saved_game'),
onPressed: () => GameUtils.deleteSavedGame(myProvider),
),
],
),
Column(
children: [
TextButton(
child: buildImageContainerWidget('button_resume_game'),
onPressed: () => GameUtils.resumeSavedGame(myProvider),
),
],
),
buildDecorationImageWidget(),
],
),
],
),
);
}
static Widget buildParameterSelector(Data myProvider, String parameterCode) {
List availableValues = myProvider.getParameterAvailableValues(parameterCode);
if (availableValues.length == 1) {
return SizedBox(height: 0.0);
}
return Table(
defaultColumnWidth: IntrinsicColumnWidth(),
children: [
TableRow(
children: [
for (var index = 0; index < availableValues.length; index++)
Column(
children: [
_buildParameterButton(myProvider, parameterCode, availableValues[index])
],
),
],
),
],
);
}
static Widget _buildParameterButton(
Data myProvider, String parameterCode, String parameterValue) {
String currentValue = myProvider.getParameterValue(parameterCode).toString();
bool isActive = (parameterValue == currentValue);
String imageAsset = parameterCode + '_' + parameterValue;
return TextButton(
child: Container(
margin: EdgeInsets.all(buttonMargin),
padding: EdgeInsets.all(buttonPadding),
decoration: BoxDecoration(
color: buttonBackgroundColor,
borderRadius: BorderRadius.circular(buttonBorderRadius),
border: Border.all(
color: isActive ? buttonBorderColorActive : buttonBorderColorInactive,
width: buttonBorderWidth,
),
),
child: buildImageWidget(imageAsset),
),
onPressed: () => myProvider.setParameterValue(parameterCode, parameterValue),
);
}
}
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:jeweled_game/provider/data.dart';
import 'package:jeweled_game/screens/home.dart';
import 'package:provider/provider.dart';
import 'package:overlay_support/overlay_support.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';
import 'package:jeweled/config/theme.dart';
import 'package:jeweled/cubit/settings_cubit.dart';
import 'package:jeweled/cubit/game_cubit.dart';
import 'package:jeweled/ui/skeleton.dart';
void main() {
void main() async {
/// Initialize packages
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => runApp(MyApp()));
await EasyLocalization.ensureInitialized();
final Directory tmpDir = await getTemporaryDirectory();
Hive.init(tmpDir.toString());
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: tmpDir,
);
runApp(
EasyLocalization(
path: 'assets/translations',
supportedLocales: const <Locale>[
Locale('en'),
Locale('fr'),
],
fallbackLocale: const Locale('en'),
useFallbackTranslations: true,
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => Data(),
child: Consumer<Data>(builder: (context, data, child) {
return OverlaySupport(
return MultiBlocProvider(
providers: [
BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()),
],
child: MaterialApp(
title: 'Jeweled',
theme: appTheme,
home: const SkeletonScreen(),
// Localization stuff
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
routes: {
Home.id: (context) => Home(),
},
),
);
}),
);
}
}
class CellLocation {
final int col;
final int row;
CellLocation({
required this.col,
required this.row,
});
factory CellLocation.go(int row, int col) {
return new CellLocation(col: col, row: row);
}
}
import 'dart:math';
import 'package:jeweled/models/game_board.dart';
import 'package:jeweled/models/game_cell.dart';
import 'package:jeweled/models/cell_location.dart';
import 'package:jeweled/models/game_settings.dart';
class Game {
final GameBoard board;
final GameSettings settings;
bool isRunning = false;
bool isFinished = false;
int availableBlocksCount = 0;
int movesCount = 0;
int score = 0;
Game({
required this.board,
required this.settings,
this.isRunning = false,
this.isFinished = false,
this.availableBlocksCount = 0,
this.movesCount = 0,
this.score = 0,
});
factory Game.createNull() {
return Game(
board: GameBoard.createNull(),
settings: GameSettings.createDefault(),
);
}
factory Game.createNew({GameSettings? gameSettings}) {
GameSettings settings = gameSettings ?? GameSettings.createDefault();
return Game(
board: GameBoard.createRandom(settings),
settings: settings,
isRunning: true,
);
}
GameCell getCell(CellLocation cellLocation) {
return this.board.cells[cellLocation.row][cellLocation.col];
}
int? getCellValue(CellLocation cellLocation) {
return this.getCell(cellLocation).value;
}
void updateCellValue(CellLocation locationToUpdate, int? value) {
this.board.cells[locationToUpdate.row][locationToUpdate.col].value = value;
}
void increaseMovesCount() {
this.movesCount += 1;
}
void increaseScore(int? count) {
this.score += (count ?? 0);
}
void updateAvailableBlocksCount() {
this.availableBlocksCount = this.getAvailableBlocks(this).length;
}
void updateGameIsRunning(bool gameIsRunning) {
this.isRunning = gameIsRunning;
}
void updateGameIsFinished(bool gameIsFinished) {
this.isFinished = gameIsFinished;
}
List<CellLocation> getSiblingCells(
final CellLocation referenceCellLocation,
List<CellLocation> siblingCells,
) {
final int boardSizeHorizontal = this.settings.boardSize;
final int boardSizeVertical = this.settings.boardSize;
final int? referenceValue = this.getCellValue(referenceCellLocation);
for (var deltaRow = -1; deltaRow <= 1; deltaRow++) {
for (var 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 (this.getCellValue(candidateLocation) == referenceValue) {
bool alreadyFound = false;
for (var 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.settings.boardSize;
final int boardSizeVertical = game.settings.boardSize;
final List<List<CellLocation>> blocks = [];
for (var row = 0; row < boardSizeVertical; row++) {
for (var 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;
blocks.forEach((List<CellLocation> foundBlock) {
foundBlock.forEach((CellLocation foundBlockCell) {
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 = this.settings.boardSize;
final int boardSizeVertical = this.settings.boardSize;
for (var row = 0; row < boardSizeVertical; row++) {
for (var col = 0; col < boardSizeHorizontal; col++) {
final CellLocation cellLocation = CellLocation.go(row, col);
if (this.getCellValue(cellLocation) != null) {
final List<CellLocation> block = this.getSiblingCells(cellLocation, []);
if (block.length >= 3) {
// found one block => ok, not locked
return true;
}
}
}
}
print('Board is locked!');
return false;
}
bool isInBoard(CellLocation cell) {
if (cell.row > 0 &&
cell.row < this.settings.boardSize &&
cell.col > 0 &&
cell.col < this.settings.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
final int maxValue = this.settings.colorsCount;
for (int i = 1; i <= maxValue; i++) {
values.add(i);
}
// Add values of current col
for (int r = 0; r <= this.settings.boardSize; r++) {
if (this.isInBoard(CellLocation.go(r, col))) {
final int? value = this.getCellValue(CellLocation.go(r, col));
if (value != null) {
values.add(value);
}
}
}
// Add values of sibling cols
for (int deltaCol = -1; deltaCol <= 1; deltaCol++) {
final int c = col + deltaCol;
for (int r = 0; r < this.settings.boardSize; r++) {
if (this.isInBoard(CellLocation.go(r, c))) {
final int? value = this.getCellValue(CellLocation.go(r, c));
if (value != null) {
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 (this.isInBoard(CellLocation.go(r, c))) {
final int? value = this.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() {
print('');
print('## Current game dump:');
print('');
this.settings.dump();
print('');
this.board.dump();
print('');
print('Game: ');
print(' isRunning: ' + isRunning.toString());
print(' isFinished: ' + isFinished.toString());
print(' movesCount: ' + movesCount.toString());
print(' score: ' + score.toString());
print(' availableBlocksCount: ' + availableBlocksCount.toString());
print('');
}
String toString() {
return 'Game(' + this.toJson().toString() + ')';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'board': this.board.toJson(),
'settings': this.settings.toJson(),
'isRunning': this.isRunning,
'isFinished': this.isFinished,
'availableBlocksCount': this.availableBlocksCount,
'movesCount': this.movesCount,
'score': this.score,
};
}
}
import 'dart:math';
import 'package:jeweled/models/game_cell.dart';
import 'package:jeweled/models/game_settings.dart';
class GameBoard {
final List<List<GameCell>> cells;
GameBoard({
required this.cells,
});
factory GameBoard.createNull() {
return GameBoard(cells: []);
}
factory GameBoard.createRandom(GameSettings gameSettings) {
final int boardSizeHorizontal = gameSettings.boardSize;
final int boardSizeVertical = gameSettings.boardSize;
final int maxValue = gameSettings.colorsCount;
final rand = new Random();
List<List<GameCell>> cells = [];
for (var rowIndex = 0; rowIndex < boardSizeVertical; rowIndex++) {
List<GameCell> row = [];
for (var colIndex = 0; colIndex < boardSizeHorizontal; colIndex++) {
int value = 1 + rand.nextInt(maxValue);
row.add(GameCell(value));
}
cells.add(row);
}
return GameBoard(
cells: cells,
);
}
void dump() {
String horizontalRule = '----';
cells[0].forEach((element) {
horizontalRule += '-';
});
print('Board:');
print(horizontalRule);
for (var rowIndex = 0; rowIndex < cells.length; rowIndex++) {
String row = '| ';
for (var colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
row += cells[rowIndex][colIndex].value.toString();
}
row += ' |';
print(row);
}
print(horizontalRule);
}
String toString() {
return 'Board(' + this.toJson().toString() + ')';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'cells': this.cells.toString(),
};
}
}
class GameCell {
int? value;
GameCell(
this.value,
);
String toString() {
return 'Cell(' + this.toJson().toString() + ')';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'value': this.value,
};
}
}
import 'package:jeweled/config/default_game_settings.dart';
class GameSettings {
final int boardSize;
final int colorsCount;
GameSettings({
required this.boardSize,
required this.colorsCount,
});
static int getBoardSizeValueFromUnsafe(int size) {
if (DefaultGameSettings.allowedBoardSizeValues.contains(size)) {
return size;
}
return DefaultGameSettings.defaultBoardSizeValue;
}
static int getColorsCountValueFromUnsafe(int colorsCount) {
if (DefaultGameSettings.allowedColorsCountValues.contains(colorsCount)) {
return colorsCount;
}
return DefaultGameSettings.defaultColorsCountValue;
}
factory GameSettings.createDefault() {
return GameSettings(
boardSize: DefaultGameSettings.defaultBoardSizeValue,
colorsCount: DefaultGameSettings.defaultColorsCountValue,
);
}
void dump() {
print('Settings: ');
print(' boardSize: ' + boardSize.toString());
print(' colorsCount: ' + colorsCount.toString());
}
String toString() {
return 'GameSettings(' + this.toJson().toString() + ')';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'boardSize': this.boardSize,
'colorsCount': this.colorsCount,
};
}
}
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:jeweled_game/entities/cell.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Data extends ChangeNotifier {
// Configuration available parameters
List _availableParameters = ['level', 'size', 'colors', 'skin'];
List _availableLevelValues = ['easy', 'medium', 'hard', 'nightmare'];
List _availableSizeValues = ['small', 'medium', 'large', 'extra'];
List _availableColorsValues = ['5', '6', '7', '8'];
List _availableSkinValues = ['default'];
List get availableParameters => _availableParameters;
List get availableLevelValues => _availableLevelValues;
List get availableSizeValues => _availableSizeValues;
List get availableSkinValues => _availableSkinValues;
// Application default configuration
String _parameterLevel = '';
String _parameterLevelDefault = 'medium';
String _parameterSize = '';
String _parameterSizeDefault = 'medium';
String _parameterColors = '';
String _parameterColorsDefault = '6';
String _parameterSkin = '';
String _parameterSkinDefault = 'default';
// Application current configuration
String get parameterLevel => _parameterLevel;
String get parameterSize => _parameterSize;
String get parameterSkin => _parameterSkin;
// Game data
bool _assetsPreloaded = false;
bool _gameIsRunning = false;
bool _gameIsFinished = false;
int _sizeVertical = 0;
int _sizeHorizontal = 0;
int _colorsCount = 0;
List<List<Cell>> _cells = [];
String _currentState = '';
int _availableBlocksCount = 0;
int _score = 0;
int _movesCount = 0;
void updateParameterLevel(String parameterLevel) {
_parameterLevel = parameterLevel;
notifyListeners();
}
int get sizeVertical => _sizeVertical;
int get sizeHorizontal => _sizeHorizontal;
void updateParameterSize(String parameterSize) {
_parameterSize = parameterSize;
updateBoardSize(getBoardSizeFromParameter(parameterSize));
notifyListeners();
}
void updateParameterColors(String parameterColors) {
_parameterColors = parameterColors;
updateColorsCount(getColorsCountFromParameter(parameterColors));
notifyListeners();
}
void updateParameterSkin(String parameterSkin) {
_parameterSkin = parameterSkin;
notifyListeners();
}
String getParameterValue(String parameterCode) {
switch (parameterCode) {
case 'level':
{
return _parameterLevel;
}
case 'size':
{
return _parameterSize;
}
case 'colors':
{
return _parameterColors;
}
case 'skin':
{
return _parameterSkin;
}
}
return '';
}
List getParameterAvailableValues(String parameterCode) {
switch (parameterCode) {
case 'level':
{
return _availableLevelValues;
}
case 'size':
{
return _availableSizeValues;
}
case 'colors':
{
return _availableColorsValues;
}
case 'skin':
{
return _availableSkinValues;
}
}
return [];
}
void setParameterValue(String parameterCode, String parameterValue) async {
switch (parameterCode) {
case 'level':
{
updateParameterLevel(parameterValue);
}
break;
case 'size':
{
updateParameterSize(parameterValue);
}
break;
case 'colors':
{
updateParameterColors(parameterValue);
}
break;
case 'skin':
{
updateParameterSkin(parameterValue);
}
break;
}
final prefs = await SharedPreferences.getInstance();
prefs.setString(parameterCode, parameterValue);
}
void initParametersValues() async {
final prefs = await SharedPreferences.getInstance();
setParameterValue('level', prefs.getString('level') ?? _parameterLevelDefault);
setParameterValue('size', prefs.getString('size') ?? _parameterSizeDefault);
setParameterValue('colors', prefs.getString('colors') ?? _parameterColorsDefault);
setParameterValue('skin', prefs.getString('skin') ?? _parameterSkinDefault);
}
String getBoardSizeFromParameter(String parameterSize) {
switch (parameterSize) {
case 'small':
{
return '6x6';
}
case 'medium':
{
return '10x10';
}
case 'large':
{
return '14x14';
}
case 'extra':
{
return '20x20';
}
}
return getBoardSizeFromParameter(_parameterSizeDefault);
}
int getColorsCountFromParameter(String parameterColors) {
switch (parameterColors) {
case '5':
{
return 5;
}
case '6':
{
return 6;
}
case '7':
{
return 7;
}
case '8':
{
return 8;
}
}
return getColorsCountFromParameter(_parameterColorsDefault);
}
void updateBoardSize(String boardSize) {
_sizeHorizontal = int.parse(boardSize.split('x')[0]);
_sizeVertical = int.parse(boardSize.split('x')[1]);
}
int get colorsCount => _colorsCount;
void updateColorsCount(int colorsCount) {
_colorsCount = colorsCount;
}
String get currentState => _currentState;
String computeCurrentGameState() {
String cellsValues = '';
for (var rowIndex = 0; rowIndex < _cells.length; rowIndex++) {
for (var colIndex = 0; colIndex < _cells[rowIndex].length; colIndex++) {
cellsValues += _cells[rowIndex][colIndex].value;
}
}
var currentState = {
'level': _parameterLevel,
'size': _parameterSize,
'skin': _parameterSkin,
'board': cellsValues,
};
return json.encode(currentState);
}
void saveCurrentGameState() async {
if (_gameIsRunning) {
_currentState = computeCurrentGameState();
final prefs = await SharedPreferences.getInstance();
prefs.setString('savedState', _currentState);
} else {
resetCurrentSavedState();
}
}
void resetCurrentSavedState() async {
_currentState = '';
final prefs = await SharedPreferences.getInstance();
prefs.setString('savedState', _currentState);
notifyListeners();
}
void loadCurrentSavedState() async {
final prefs = await SharedPreferences.getInstance();
_currentState = prefs.getString('savedState') ?? '';
}
bool hasCurrentSavedState() {
return (_currentState != '');
}
Map<String, dynamic> getCurrentSavedState() {
if (_currentState != '') {
Map<String, dynamic> savedState = json.decode(_currentState);
if (savedState.isNotEmpty) {
return savedState;
}
}
return {};
}
bool get assetsPreloaded => _assetsPreloaded;
void updateAssetsPreloaded(bool assetsPreloaded) {
_assetsPreloaded = assetsPreloaded;
}
List<List<Cell>> get cells => _cells;
void updateCells(List<List<Cell>> cells) {
_cells = cells;
notifyListeners();
}
Cell getCell(int row, int col) {
return cells[row][col];
}
String getCellValue(int row, int col) {
return getCell(row, col).value;
}
updateCellValue(int col, int row, String value) {
_cells[row][col].value = value;
notifyListeners();
}
bool get isGameRunning => _gameIsRunning;
void updateGameIsRunning(bool gameIsRunning) {
_gameIsRunning = gameIsRunning;
notifyListeners();
}
bool get isGameFinished => _gameIsFinished;
void updateGameIsFinished(bool gameIsFinished) {
_gameIsFinished = gameIsFinished;
notifyListeners();
}
int get availableBlocksCount => _availableBlocksCount;
void updateAvailableBlocksCount(int availableBlocksCount) {
_availableBlocksCount = availableBlocksCount;
notifyListeners();
}
int get score => _score;
void increaseScore(int increment) {
_score += increment;
notifyListeners();
}
int get movesCount => _movesCount;
void increaseMovesCount() {
_movesCount++;
notifyListeners();
}
void resetGame() {
_gameIsRunning = false;
_gameIsFinished = false;
_availableBlocksCount = 0;
_score = 0;
_movesCount = 0;
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:jeweled_game/layout/game.dart';
import 'package:jeweled_game/layout/parameters.dart';
import 'package:jeweled_game/provider/data.dart';
import 'package:jeweled_game/utils/game_utils.dart';
import 'package:provider/provider.dart';
import 'package:overlay_support/overlay_support.dart';
class Home extends StatefulWidget {
static const String id = 'home';
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
super.initState();
Data myProvider = Provider.of<Data>(context, listen: false);
myProvider.initParametersValues();
myProvider.loadCurrentSavedState();
}
List getImagesAssets(Data myProvider) {
List assets = [];
List gameImages = [
'button_back',
'button_delete_saved_game',
'button_resume_game',
'button_start',
'game_fail',
'game_win',
'placeholder',
];
myProvider.availableLevelValues.forEach((level) => gameImages.add('level_' + level));
myProvider.availableSizeValues.forEach((size) => gameImages.add('size_' + size));
myProvider.availableSkinValues.forEach((skin) => gameImages.add('skin_' + skin));
gameImages.forEach((image) => assets.add('assets/icons/' + image + '.png'));
List skinImages = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
];
myProvider.availableSkinValues.forEach((skin) => skinImages
.forEach((image) => assets.add('assets/skins/' + skin + '_' + image + '.png')));
return assets;
}
@override
Widget build(BuildContext context) {
Data myProvider = Provider.of<Data>(context);
if (!myProvider.assetsPreloaded) {
List assets = getImagesAssets(myProvider);
assets.forEach((asset) => precacheImage(AssetImage(asset), context));
myProvider.updateAssetsPreloaded(true);
}
List<Widget> menuActions = [];
if (myProvider.isGameRunning) {
menuActions = [
TextButton(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.blue,
width: 4,
),
),
child: Image(
image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill,
),
),
onPressed: () => toast('Long press to quit game...'),
onLongPress: () => GameUtils.quitGame(myProvider),
),
];
}
return Scaffold(
appBar: AppBar(
actions: menuActions,
),
body: SafeArea(
child: Center(
child: myProvider.isGameRunning
? Game.buildGameWidget(myProvider)
: Parameters.buildParametersSelector(myProvider),
),
),
);
}
}
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:jeweled/models/game_cell.dart';
import 'package:jeweled/models/game.dart';
import 'package:jeweled/models/cell_location.dart';
import 'package:jeweled/utils/color_theme.dart';
class GameBoardPainter extends CustomPainter {
const GameBoardPainter(this.currentGame);
final Game currentGame;
static const drawTextValue = false;
@override
void paint(Canvas canvas, Size size) {
final int sizeHorizontal = currentGame.settings.boardSize;
final int sizeVertical = currentGame.settings.boardSize;
final List cells = currentGame.board.cells;
final double cellSize = size.width / max(sizeHorizontal, sizeVertical);
// background
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 colorCode = ColorTheme.getColorCode(cell.value);
final cellPaintBackground = Paint();
cellPaintBackground.color = Color(colorCode);
cellPaintBackground.style = PaintingStyle.fill;
final Rect cellBackground =
Rect.fromPoints(Offset(x - 1, y - 1), Offset(x + cellSize + 2, y + cellSize + 2));
canvas.drawRect(cellBackground, cellPaintBackground);
// draw value on cell
if (drawTextValue) {
final textPainter = TextPainter(
text: TextSpan(
text: cell.value.toString(),
style: const TextStyle(
color: Colors.black,
fontSize: 15,
),
),
textDirection: TextDirection.ltr,
)..layout(
minWidth: 0,
maxWidth: size.width,
);
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
if ((row == sizeVertical - 1) ||
((row + 1) < sizeVertical &&
cellValue != currentGame.getCellValue(CellLocation.go(row + 1, col)))) {
final Offset borderStart = Offset(x, y + cellSize);
final Offset borderStop = Offset(x + cellSize, y + cellSize);
canvas.drawLine(borderStart, borderStop, cellPaintBorder);
}
// left border
if ((col == 0) ||
(col > 1 &&
cellValue != currentGame.getCellValue(CellLocation.go(row, col - 1)))) {
final Offset borderStart = Offset(x, y);
final Offset borderStop = Offset(x, y + cellSize);
canvas.drawLine(borderStart, borderStop, cellPaintBorder);
}
// right border
if ((col == sizeHorizontal - 1) ||
((col + 1) < sizeHorizontal &&
cellValue != currentGame.getCellValue(CellLocation.go(row, col + 1)))) {
final Offset borderStart = Offset(x + cellSize, y);
final Offset borderStop = Offset(x + cellSize, y + cellSize);
canvas.drawLine(borderStart, borderStop, cellPaintBorder);
}
}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}