Skip to content
Snippets Groups Projects
Commit 060a001d authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Merge branch '1-first-game-release' into 'master'

Resolve "First game release"

Closes #1

See merge request !1
parents dca4ec14 816b2e33
No related branches found
Tags Release_0.0.1_1
1 merge request!1Resolve "First game release"
Pipeline #7770 passed
Showing
with 2338 additions and 0 deletions
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
gradlew 0 → 100755
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/ui/pages/game.dart';
import 'package:suguru/ui/parameters/parameter_painter_board_size.dart';
import 'package:suguru/ui/parameters/parameter_painter_difficulty_level.dart';
class ApplicationConfig {
// activity parameter: difficulty level
static const String parameterCodeDifficultyLevel = 'activity.difficultyLevel';
static const String difficultyLevelValueEasy = 'easy';
static const String difficultyLevelValueMedium = 'medium';
static const String difficultyLevelValueHard = 'hard';
// activity parameter: board size
static const String parameterCodeBoardSize = 'activity.boardSize';
static const String boardSizeValueSmall = 'small';
static const String boardSizeValueMedium = 'medium';
static const String boardSizeValueLarge = 'large';
// activity parameter: skin
static const String parameterCodeSkin = 'global.skin';
static const String skinValueDigits = 'digits';
static const int defaultTipCountDownValueInSeconds = 20;
// activity pages
static const int activityPageIndexHome = 0;
static const int activityPageIndexGame = 1;
static final ApplicationConfigDefinition config = ApplicationConfigDefinition(
appTitle: 'Suguru',
activitySettings: [
// difficulty level
ApplicationSettingsParameter(
code: parameterCodeDifficultyLevel,
values: [
ApplicationSettingsParameterItemValue(
value: difficultyLevelValueEasy,
color: Colors.green,
),
ApplicationSettingsParameterItemValue(
value: difficultyLevelValueMedium,
color: Colors.orange,
isDefault: true,
),
ApplicationSettingsParameterItemValue(
value: difficultyLevelValueHard,
color: Colors.red,
),
],
customPainter: (context, value) => ParameterPainterDifficultyLevel(
context: context,
value: value,
),
),
// board size
ApplicationSettingsParameter(
code: parameterCodeBoardSize,
values: [
ApplicationSettingsParameterItemValue(
value: boardSizeValueSmall,
color: Colors.green,
),
ApplicationSettingsParameterItemValue(
value: boardSizeValueMedium,
color: Colors.orange,
),
ApplicationSettingsParameterItemValue(
value: boardSizeValueLarge,
color: Colors.red,
isDefault: true,
),
],
customPainter: (context, value) => ParameterPainterBoardSize(
context: context,
value: value,
),
),
// skin
ApplicationSettingsParameter(
code: parameterCodeSkin,
values: [
ApplicationSettingsParameterItemValue(
value: skinValueDigits,
isDefault: true,
),
],
builder: ({
required context,
required itemValue,
required onPressed,
required size,
}) =>
StyledButton(
color: Colors.green.shade800,
onPressed: onPressed,
child: Image(
image: AssetImage('assets/ui/skin_${itemValue.value}.png'),
fit: BoxFit.fill,
),
),
),
],
startNewActivity: (BuildContext context) {
BlocProvider.of<ActivityCubit>(context).startNewActivity(context);
BlocProvider.of<NavCubitPage>(context)
.updateIndex(ApplicationConfig.activityPageIndexGame);
},
quitCurrentActivity: (BuildContext context) {
BlocProvider.of<ActivityCubit>(context).quitActivity();
BlocProvider.of<NavCubitPage>(context)
.updateIndex(ApplicationConfig.activityPageIndexHome);
},
deleteCurrentActivity: (BuildContext context) {
BlocProvider.of<ActivityCubit>(context).deleteSavedActivity();
},
resumeActivity: (BuildContext context) {
BlocProvider.of<ActivityCubit>(context).resumeSavedActivity();
BlocProvider.of<NavCubitPage>(context)
.updateIndex(ApplicationConfig.activityPageIndexGame);
},
navigation: ApplicationNavigation(
screenActivity: ScreenItem(
code: 'screen_activity',
icon: Icon(UniconsLine.home),
screen: ({required ApplicationConfigDefinition appConfig}) =>
ScreenActivity(appConfig: appConfig),
),
screenSettings: ScreenItem(
code: 'screen_settings',
icon: Icon(UniconsLine.setting),
screen: ({required ApplicationConfigDefinition appConfig}) => ScreenSettings(),
),
screenAbout: ScreenItem(
code: 'screen_about',
icon: Icon(UniconsLine.info_circle),
screen: ({required ApplicationConfigDefinition appConfig}) => ScreenAbout(),
),
appBarConfiguration: AppBarConfiguration(
hideApplicationTitle: true,
pushQuitActivityButtonLeft: true,
),
activityPages: {
activityPageIndexHome: ActivityPageItem(
code: 'page_home',
icon: Icon(UniconsLine.home),
builder: ({required ApplicationConfigDefinition appConfig}) {
return BlocBuilder<ActivityCubit, ActivityState>(
builder: (BuildContext context, ActivityState activityState) {
return PageParameters(
appConfig: appConfig,
canBeResumed: activityState.currentActivity.canBeResumed,
);
},
);
},
),
activityPageIndexGame: ActivityPageItem(
code: 'page_game',
icon: Icon(UniconsLine.star),
builder: ({required ApplicationConfigDefinition appConfig}) {
return BlocBuilder<ActivityCubit, ActivityState>(
builder: (BuildContext context, ActivityState activityState) {
return PageGame();
},
);
},
),
},
),
);
}
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/config/application_config.dart';
import 'package:suguru/models/activity/cell.dart';
import 'package:suguru/models/activity/cell_location.dart';
import 'package:suguru/models/activity/activity.dart';
import 'package:suguru/utils/board_animate.dart';
part 'activity_state.dart';
class ActivityCubit extends HydratedCubit<ActivityState> {
ActivityCubit()
: super(ActivityState(
currentActivity: Activity.createEmpty(),
));
void updateState(Activity activity) {
emit(ActivityState(
currentActivity: activity,
));
}
void refresh() {
final Activity activity = Activity(
// Settings
activitySettings: state.currentActivity.activitySettings,
// State
isRunning: state.currentActivity.isRunning,
isStarted: state.currentActivity.isStarted,
isFinished: state.currentActivity.isFinished,
animationInProgress: state.currentActivity.animationInProgress,
boardAnimated: state.currentActivity.boardAnimated,
// Base data
board: state.currentActivity.board,
boardSizeHorizontal: state.currentActivity.boardSizeHorizontal,
boardSizeVertical: state.currentActivity.boardSizeVertical,
// Game data
boardConflicts: state.currentActivity.boardConflicts,
selectedCell: state.currentActivity.selectedCell,
showConflicts: state.currentActivity.showConflicts,
givenTipsCount: state.currentActivity.givenTipsCount,
buttonTipsCountdown: state.currentActivity.buttonTipsCountdown,
);
// activity.dump();
updateState(activity);
}
void startNewActivity(BuildContext context) {
final ActivitySettingsCubit activitySettingsCubit =
BlocProvider.of<ActivitySettingsCubit>(context);
final Activity newActivity = Activity.createNew(
// Settings
activitySettings: activitySettingsCubit.state.settings,
);
newActivity.dump();
updateState(newActivity);
refresh();
BoardAnimate.startAnimation(this, 'start');
}
void selectCell(CellLocation location) {
state.currentActivity.selectedCell = state.currentActivity.board.get(location);
refresh();
}
void unselectCell() {
state.currentActivity.selectedCell = null;
refresh();
}
void updateCellValue(CellLocation location, int value) {
final cell = state.currentActivity.board.get(location);
if (cell.isFixed == false) {
state.currentActivity.board.setCell(
location,
Cell(
location: location,
blockId: cell.blockId,
value: value,
isFixed: false,
),
);
state.currentActivity.isStarted = true;
refresh();
}
if (state.currentActivity.checkBoardIsSolved()) {
BoardAnimate.startAnimation(this, 'win');
state.currentActivity.isFinished = true;
refresh();
}
}
void toggleShowConflicts() {
state.currentActivity.showConflicts = !state.currentActivity.showConflicts;
refresh();
}
void increaseGivenTipsCount() {
state.currentActivity.givenTipsCount++;
state.currentActivity.buttonTipsCountdown =
ApplicationConfig.defaultTipCountDownValueInSeconds;
refresh();
const Duration interval = Duration(milliseconds: 500);
Timer.periodic(
interval,
(Timer timer) {
if (state.currentActivity.buttonTipsCountdown == 0) {
timer.cancel();
} else {
state.currentActivity.buttonTipsCountdown =
max(state.currentActivity.buttonTipsCountdown - 1, 0);
}
refresh();
},
);
}
bool canBeResumed() {
return state.currentActivity.canBeResumed;
}
void quitActivity() {
state.currentActivity.isRunning = false;
refresh();
}
void resumeSavedActivity() {
state.currentActivity.isRunning = true;
refresh();
}
void deleteSavedActivity() {
state.currentActivity.isRunning = false;
state.currentActivity.isFinished = true;
refresh();
}
void updateAnimationInProgress(bool animationInProgress) {
state.currentActivity.animationInProgress = animationInProgress;
refresh();
}
void setAnimatedBackground(List animatedCellsPattern) {
for (int row = 0; row < state.currentActivity.boardSizeVertical; row++) {
for (int col = 0; col < state.currentActivity.boardSizeHorizontal; col++) {
state.currentActivity.boardAnimated[row][col] = animatedCellsPattern[row][col];
}
}
refresh();
}
void resetAnimatedBackground() {
for (int row = 0; row < state.currentActivity.boardSizeVertical; row++) {
for (int col = 0; col < state.currentActivity.boardSizeHorizontal; col++) {
state.currentActivity.boardAnimated[row][col] = false;
}
}
refresh();
}
@override
ActivityState? fromJson(Map<String, dynamic> json) {
final Activity currentActivity = json['currentActivity'] as Activity;
return ActivityState(
currentActivity: currentActivity,
);
}
@override
Map<String, dynamic>? toJson(ActivityState state) {
return <String, dynamic>{
'currentActivity': state.currentActivity.toJson(),
};
}
}
part of 'activity_cubit.dart';
@immutable
class ActivityState extends Equatable {
const ActivityState({
required this.currentActivity,
});
final Activity currentActivity;
@override
List<dynamic> get props => <dynamic>[
currentActivity,
];
}
class GameData {
static const Map<String, Map<String, List<String>>> templates = {
// 'size': {
// 'level': [
// '[width]x[height];[blocks];[init]',
// ],
// },
'small': {
'easy': [
// ok
'5x5;AABBBAABBCADECCDDCCFGGGGG;0205030001000300000040003',
],
'medium': [
// duplicate // to replace
'5x5;AAABCAADBBEEEEBEFFFBEEGGG;1000002004504000000000000',
],
'hard': [
// to check
'5x5;AAABCAADBBEEEEBEFFFBEEGGG;1000002004504000000000000',
],
},
'medium': {
'easy': [
// to check
'8x5;ABBBBCDDAABECCDDFAAECCGDFFEEHHGGFFEHHHIG;0005005040300404020500001040000002000500',
],
'medium': [
// to check
'8x5;AABBCCCDAABBCCDDAEBFFGDHIIIFFJJHIIKFJJJH;2020003000000000003050005000000000001050',
],
'hard': [
// to check
'8x5;AABCCCDDEEBBCCDDEEFFFGGDEHHFFGGGHHHIIIII;0000100500000200000500014030005001001000',
],
},
'large': {
'easy': [
// duplicate // to replace
'5x11;ABBCCDBBCCDDBCEDDFFEGGHEEGGHEIJGHIIJHHKIJLKKILLKMMLLMMM;0003040004000000200000030200000400000003300000400520000',
],
'medium': [
// to check
'5x11;ABBCCDBBCCDDBCEDDFFEGGHEEGGHEIJGHIIJHHKIJLKKILLKMMLLMMM;0003040004000000200000030200000400000003300000400520000',
],
'hard': [
// duplicate // to replace
'5x11;ABBCCDBBCCDDBCEDDFFEGGHEEGGHEIJGHIIJHHKIJLKKILLKMMLLMMM;0003040004000000200000030200000400000003300000400520000',
],
},
};
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/config/application_config.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/ui/skeleton.dart';
void main() async {
// Initialize packages
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
final Directory tmpDir = await getTemporaryDirectory();
Hive.init(tmpDir.toString());
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: tmpDir,
);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => 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) {
final List<String> assets = getImagesAssets();
for (String asset in assets) {
precacheImage(AssetImage(asset), context);
}
return MultiBlocProvider(
providers: [
// default providers
BlocProvider<NavCubitPage>(
create: (context) => NavCubitPage(appConfig: ApplicationConfig.config),
),
BlocProvider<NavCubitScreen>(
create: (context) => NavCubitScreen(),
),
BlocProvider<ApplicationThemeModeCubit>(
create: (context) => ApplicationThemeModeCubit(),
),
BlocProvider<ActivityCubit>(
create: (context) => ActivityCubit(),
),
BlocProvider<ActivitySettingsCubit>(
create: (context) => ActivitySettingsCubit(appConfig: ApplicationConfig.config),
),
],
child: BlocBuilder<ApplicationThemeModeCubit, ApplicationThemeModeState>(
builder: (BuildContext context, ApplicationThemeModeState state) {
return MaterialApp(
title: ApplicationConfig.config.appTitle,
home: const SkeletonScreen(),
// Theme stuff
theme: lightTheme,
darkTheme: darkTheme,
themeMode: state.themeMode,
// Localization stuff
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
);
},
),
);
}
List<String> getImagesAssets() {
final List<String> assets = [];
const List<String> gameImages = [
'button_help',
'button_show_conflicts',
'cell_empty',
'game_win',
'placeholder',
];
for (String image in gameImages) {
assets.add('assets/ui/$image.png');
}
List<String> skinImages = [];
for (int value = 1; value <= 9; value++) {
skinImages.add(value.toString());
}
for (String skin in ApplicationConfig.config
.getFromCode(ApplicationConfig.parameterCodeSkin)
.allowedValues) {
assets.add('assets/ui/skin_$skin.png');
for (String image in skinImages) {
assets.add('assets/skins/${skin}_$image.png');
}
}
return assets;
}
}
import 'dart:math';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/config/application_config.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/data/game_data.dart';
import 'package:suguru/models/activity/board.dart';
import 'package:suguru/models/activity/cell.dart';
import 'package:suguru/models/activity/cell_location.dart';
import 'package:suguru/models/activity/types.dart';
class Activity {
Activity({
// Settings
required this.activitySettings,
// State
this.isRunning = false,
this.isStarted = false,
this.isFinished = false,
this.animationInProgress = false,
this.boardAnimated = const [],
// Base data
required this.board,
required this.boardSizeHorizontal,
required this.boardSizeVertical,
// Game data
this.boardConflicts = const [],
this.selectedCell,
this.showConflicts = false,
this.givenTipsCount = 0,
this.buttonTipsCountdown = 0,
});
// Settings
final ActivitySettings activitySettings;
// State
bool isRunning;
bool isStarted;
bool isFinished;
bool animationInProgress;
AnimatedBoard boardAnimated;
// Base data
final Board board;
final int boardSizeHorizontal;
final int boardSizeVertical;
// Game data
ConflictsCount boardConflicts;
Cell? selectedCell;
bool showConflicts;
int givenTipsCount;
int buttonTipsCountdown;
factory Activity.createEmpty() {
return Activity(
// Settings
activitySettings: ActivitySettings.createDefault(appConfig: ApplicationConfig.config),
// Base data
board: Board.createEmpty(),
boardSizeHorizontal: 0,
boardSizeVertical: 0,
// Game data
givenTipsCount: 0,
);
}
factory Activity.createNew({
ActivitySettings? activitySettings,
}) {
final ActivitySettings newActivitySettings = activitySettings ??
ActivitySettings.createDefault(appConfig: ApplicationConfig.config);
final List<String> templates =
GameData.templates[newActivitySettings.get(ApplicationConfig.parameterCodeBoardSize)]
?[newActivitySettings.get(ApplicationConfig.parameterCodeDifficultyLevel)] ??
[];
final String template = templates.elementAt(Random().nextInt(templates.length)).toString();
final List<String> templateParts = template.split(';');
if (templateParts.length != 3) {
printlog('Failed to get grid template (wrong format)...');
return Activity.createEmpty();
}
final String boardSizeAsString = templateParts[0];
final int boardSizeHorizontal = int.parse(boardSizeAsString.split('x')[0]);
final int boardSizeVertical = int.parse(boardSizeAsString.split('x')[1]);
// Build empty conflicts board
ConflictsCount nonConflictedBoard = [];
for (int row = 0; row < boardSizeVertical; row++) {
List<int> line = [];
for (int col = 0; col < boardSizeHorizontal; col++) {
line.add(0);
}
nonConflictedBoard.add(line);
}
// Build empty animated background
AnimatedBoard notAnimatedBoard = [];
for (int row = 0; row < boardSizeVertical; row++) {
List<bool> line = [];
for (int col = 0; col < boardSizeHorizontal; col++) {
line.add(false);
}
notAnimatedBoard.add(line);
}
// Build main game board
final Board board = Board.createEmpty();
board.createFromTemplate(template: template);
return Activity(
// Settings
activitySettings: newActivitySettings,
// State
isRunning: true,
boardAnimated: notAnimatedBoard,
// Base data
board: board,
boardSizeHorizontal: boardSizeHorizontal,
boardSizeVertical: boardSizeVertical,
// Game data
boardConflicts: nonConflictedBoard,
selectedCell: null,
);
}
bool get canBeResumed => isStarted && !isFinished;
bool get gameWon => isRunning && isStarted && isFinished;
bool get canGiveTip => (buttonTipsCountdown == 0);
bool checkBoardIsSolved() {
for (CellLocation location in board.getCellLocations()) {
if (board.get(location).value == 0 ||
board.get(location).value != board.solvedCells[location.row][location.col].value) {
return false;
}
}
printlog('-> ok suguru solved!');
return true;
}
ConflictsCount computeConflictsInBoard() {
final BoardCells cells = board.cells;
final ConflictsCount conflicts = boardConflicts;
// reset conflict states
for (int row = 0; row < boardSizeVertical; row++) {
for (int col = 0; col < boardSizeHorizontal; col++) {
conflicts[row][col] = 0;
}
}
// check siblings
for (int row = 0; row < boardSizeVertical; row++) {
for (int col = 0; col < boardSizeHorizontal; col++) {
final int value = cells[row][col].value;
if (value != 0) {
for (int deltaRow = -1; deltaRow <= 1; deltaRow++) {
for (int deltaCol = -1; deltaCol <= 1; deltaCol++) {
if (row + deltaRow >= 0 &&
row + deltaRow < boardSizeHorizontal &&
col + deltaCol >= 0 &&
col + deltaCol < boardSizeVertical &&
(deltaRow * deltaCol != 0)) {
final int siblingValue = cells[row + deltaRow][col + deltaCol].value;
if (siblingValue == value) {
conflicts[row][col]++;
}
}
}
}
}
}
}
// check blocks does not contain duplicates
for (String blockId in board.getBlockIds()) {
List<int> values = [];
List<int> duplicateValues = [];
for (int row = 0; row < boardSizeVertical; row++) {
for (int col = 0; col < boardSizeHorizontal; col++) {
if (cells[row][col].blockId == blockId) {
final int value = cells[row][col].value;
if (value != 0) {
if (!values.contains(value)) {
values.add(value);
} else {
duplicateValues.add(value);
}
}
}
}
}
for (int duplicateValue in duplicateValues) {
for (int row = 0; row < boardSizeVertical; row++) {
for (int col = 0; col < boardSizeHorizontal; col++) {
if (cells[row][col].blockId == blockId &&
cells[row][col].value == duplicateValue) {
conflicts[row][col]++;
}
}
}
}
}
return conflicts;
}
void showTip(ActivityCubit activityCubit) {
bool tipGiven = false;
if (selectedCell == null) {
// no selected cell -> pick one
tipGiven = helpSelectCell(activityCubit);
} else {
// currently selected cell -> set value
tipGiven = helpFillCell(activityCubit);
}
if (tipGiven) {
activityCubit.increaseGivenTipsCount();
}
}
bool helpSelectCell(ActivityCubit activityCubit) {
// pick one of wrong value cells, if found
final List<List<int>> wrongValueCells = getCellsWithWrongValue();
if (wrongValueCells.isNotEmpty) {
printlog('pick from wrongValueCells');
return pickRandomFromList(activityCubit, wrongValueCells);
}
// pick one of conflicting cells, if found
final List<List<int>> conflictingCells = getCellsWithConflicts();
if (conflictingCells.isNotEmpty) {
printlog('pick from conflictingCells');
return pickRandomFromList(activityCubit, conflictingCells);
}
// pick one from "easy" cells (unique empty cell in block)
final List<List<int>> easyFillableCells = board.getLastEmptyCellsInBlocks();
if (easyFillableCells.isNotEmpty) {
printlog('pick from easyFillableCells');
return pickRandomFromList(activityCubit, easyFillableCells);
}
// pick one from cells with unique non-conflicting candidate value
final List<List<int>> candidateCells = board.getEmptyCellsWithUniqueAvailableValue();
if (candidateCells.isNotEmpty) {
printlog('pick from candidateCells');
return pickRandomFromList(activityCubit, candidateCells);
}
// pick one from "only cell in this block for this value"
final List<List<int>> onlyCellsWithoutConflict = board.getOnlyCellInBlockWithoutConflict();
if (onlyCellsWithoutConflict.isNotEmpty) {
printlog('pick from onlyCellsWithoutConflict');
return pickRandomFromList(activityCubit, onlyCellsWithoutConflict);
}
printlog('no easy cell to select...');
return false;
}
List<List<int>> getCellsWithWrongValue() {
final BoardCells cells = board.cells;
final BoardCells cellsSolved = board.solvedCells;
List<List<int>> cellsWithWrongValue = [];
for (int row = 0; row < boardSizeVertical; row++) {
for (int col = 0; col < boardSizeHorizontal; col++) {
if (cells[row][col].value != 0 &&
cells[row][col].value != cellsSolved[row][col].value) {
cellsWithWrongValue.add([row, col]);
}
}
}
return cellsWithWrongValue;
}
List<List<int>> getCellsWithConflicts() {
List<List<int>> cellsWithConflict = [];
for (int row = 0; row < boardSizeVertical; row++) {
for (int col = 0; col < boardSizeHorizontal; col++) {
if (boardConflicts[row][col] != 0) {
cellsWithConflict.add([row, col]);
}
}
}
return cellsWithConflict;
}
bool pickRandomFromList(ActivityCubit activityCubit, List<List<int>> cellsCoordinates) {
if (cellsCoordinates.isNotEmpty) {
cellsCoordinates.shuffle();
final List<int> cell = cellsCoordinates[0];
activityCubit.selectCell(CellLocation.go(cell[0], cell[1]));
return true;
}
return false;
}
bool helpFillCell(ActivityCubit activityCubit) {
// Will clean cell if no eligible value found
int eligibleValue = 0;
// Ensure there is only one eligible value for this cell
int allowedValuesCount = 0;
final int maxValueForThisCell = board.getMaxValueForBlock(selectedCell?.blockId);
for (int value = 1; value <= maxValueForThisCell; value++) {
if (board.isValueAllowed(selectedCell?.location, value)) {
allowedValuesCount++;
eligibleValue = value;
}
}
if (allowedValuesCount == 1) {
activityCubit.updateCellValue(selectedCell!.location, eligibleValue);
activityCubit.unselectCell();
return true;
}
activityCubit.unselectCell();
return false;
}
void checkAllTemplates() {
printlog('###############################');
printlog('## ##');
printlog('## CHECK TEMPLATES ##');
printlog('## ##');
printlog('###############################');
final List<String> allowedLevels = ApplicationConfig.config
.getFromCode(ApplicationConfig.parameterCodeDifficultyLevel)
.allowedValues;
final List<String> allowedSizes = ApplicationConfig.config
.getFromCode(ApplicationConfig.parameterCodeBoardSize)
.allowedValues;
for (String level in allowedLevels) {
printlog('* level: $level');
for (String size in allowedSizes) {
printlog('** size: $size');
final List<String> templates = GameData.templates[size]?[level] ?? [];
printlog('*** templates count: ${templates.length}');
for (String template in templates) {
printlog(' checking $template');
final Board testBoard = Board.createEmpty();
testBoard.createFromTemplate(template: template);
testBoard.dump();
}
}
}
}
void dump() {
printlog('');
printlog('## Current game dump:');
printlog('');
printlog('$Activity:');
printlog(' Settings');
activitySettings.dump();
printlog(' State');
printlog(' isRunning: $isRunning');
printlog(' isStarted: $isStarted');
printlog(' isFinished: $isFinished');
printlog(' animationInProgress: $animationInProgress');
printlog(' Base data');
printlog(' boardSizeHorizontal: $boardSizeHorizontal');
printlog(' boardSizeVertical: $boardSizeVertical');
printlog(' Game data');
printlog(' selectedCell: ${selectedCell?.toString() ?? ''}');
printlog(' showConflicts: $showConflicts');
printlog(' givenTipsCount: $givenTipsCount');
printlog(' buttonTipsCountdown: $buttonTipsCountdown');
printlog('');
board.dump();
printlog('');
}
@override
String toString() {
return '$Activity(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
// Settings
'activitySettings': activitySettings.toJson(),
// State
'isRunning': isRunning,
'isStarted': isStarted,
'isFinished': isFinished,
'animationInProgress': animationInProgress,
'boardAnimated': boardAnimated,
// Base data
'board': board.toJson(),
'boardSizeHorizontal': boardSizeHorizontal,
'boardSizeVertical': boardSizeVertical,
// Game data
'boardConflicts': boardConflicts,
'selectedCell': selectedCell?.toJson(),
'showConflicts': showConflicts,
'givenTipsCount': givenTipsCount,
'buttonTipsCountdown': buttonTipsCountdown,
};
}
}
import 'dart:math';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/models/activity/cell.dart';
import 'package:suguru/models/activity/cell_location.dart';
import 'package:suguru/models/activity/types.dart';
class Board {
Board({
required this.cells,
required this.solvedCells,
});
BoardCells cells = const [];
BoardCells solvedCells = const [];
factory Board.createEmpty() {
return Board(
cells: [],
solvedCells: [],
);
}
factory Board.createFromCells({
required BoardCells cells,
required BoardCells solvedCells,
}) {
return Board(
cells: cells,
solvedCells: solvedCells,
);
}
void createFromTemplate({
required String template,
}) {
final List<String> templateParts = template.split(';');
if (templateParts.length != 3) {
printlog('Failed to get grid template (wrong format)...');
}
final String boardSizeAsString = templateParts[0];
final String blocksDefinitionAsString = templateParts[1];
final String fixedCellsDefinitionAsString = templateParts[2];
final int boardSizeHorizontal = int.parse(boardSizeAsString.split('x')[0]);
final int boardSizeVertical = int.parse(boardSizeAsString.split('x')[1]);
const String stringValues = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
int index = 0;
for (int rowIndex = 0; rowIndex < boardSizeVertical; rowIndex++) {
final List<Cell> row = [];
for (int colIndex = 0; colIndex < boardSizeHorizontal; colIndex++) {
final String blockId = blocksDefinitionAsString[index];
final String cellValueAsString = fixedCellsDefinitionAsString[index];
index++;
final int cellValue = stringValues.indexOf(cellValueAsString);
row.add(Cell(
location: CellLocation.go(rowIndex, colIndex),
blockId: blockId,
value: cellValue,
isFixed: (cellValue != 0),
));
}
cells.add(row);
}
const List<String> allowedFlip = ['none', 'horizontal', 'vertical'];
List<String> allowedRotate = ['none', 'left', 'right', 'upsidedown'];
// Limit rotation if board is not symetric
if (boardSizeVertical != boardSizeHorizontal) {
allowedRotate = ['none', 'upsidedown'];
}
final Random rand = Random();
final String flip = allowedFlip[rand.nextInt(allowedFlip.length)];
final String rotate = allowedRotate[rand.nextInt(allowedRotate.length)];
switch (flip) {
case 'horizontal':
{
transformFlipHorizontal();
}
break;
case 'vertical':
{
transformFlipVertical();
}
break;
}
switch (rotate) {
case 'left':
{
transformRotateLeft();
}
break;
case 'right':
{
transformRotateRight();
}
break;
case 'upsidedown':
{
transformFlipHorizontal();
transformFlipVertical();
}
break;
}
// Force cells fixed states (all cells with value != 0)
for (CellLocation location in getCellLocations()) {
final Cell cell = get(location);
cells[location.row][location.col] = Cell(
location: location,
blockId: cell.blockId,
value: cell.value,
isFixed: (cell.value != 0) ? true : false,
);
}
resolve();
}
// Helper to create board from size, with "empty" cells
static BoardCells createEmptyBoard(final int width, final int height) {
final BoardCells cells = [];
for (int rowIndex = 0; rowIndex < height; rowIndex++) {
final List<Cell> row = [];
for (int colIndex = 0; colIndex < width; colIndex++) {
row.add(Cell(
location: CellLocation.go(rowIndex, colIndex),
blockId: '',
value: 0,
isFixed: false,
));
}
cells.add(row);
}
return cells;
}
List<CellLocation> getCellLocations([String? blockId]) {
if (cells.isEmpty) {
return [];
}
final List<CellLocation> locations = [];
final int boardSizeVertical = cells.length;
final int boardSizeHorizontal = cells[0].length;
for (int row = 0; row < boardSizeVertical; row++) {
for (int col = 0; col < boardSizeHorizontal; col++) {
if ((blockId == null) || (blockId == get(CellLocation.go(row, col)).blockId)) {
locations.add(CellLocation.go(row, col));
}
}
}
return locations;
}
void transformFlipHorizontal() {
final BoardCells transformedBoard = copyCells();
final int boardSizeVertical = cells.length;
for (CellLocation location in getCellLocations()) {
final Cell cell = cells[boardSizeVertical - location.row - 1][location.col];
transformedBoard[location.row][location.col] = Cell(
location: location,
blockId: cell.blockId,
value: cell.value,
isFixed: false,
);
}
cells = transformedBoard;
}
void transformFlipVertical() {
if (cells.isEmpty) {
return;
}
final BoardCells transformedBoard = copyCells();
final int boardSizeHorizontal = cells[0].length;
for (CellLocation location in getCellLocations()) {
final Cell cell = cells[location.row][boardSizeHorizontal - location.col - 1];
transformedBoard[location.row][location.col] = Cell(
location: location,
blockId: cell.blockId,
value: cell.value,
isFixed: false,
);
}
cells = transformedBoard;
}
void transformRotateLeft() {
final BoardCells transformedBoard = copyCells();
final int boardSizeVertical = cells.length;
for (CellLocation location in getCellLocations()) {
final Cell cell = cells[location.col][boardSizeVertical - location.row - 1];
transformedBoard[location.row][location.col] = Cell(
location: location,
blockId: cell.blockId,
value: cell.value,
isFixed: false,
);
}
cells = transformedBoard;
}
void transformRotateRight() {
if (cells.isEmpty) {
return;
}
final BoardCells transformedBoard = copyCells();
final int boardSizeHorizontal = cells[0].length;
for (CellLocation location in getCellLocations()) {
final Cell cell = cells[boardSizeHorizontal - location.col - 1][location.row];
transformedBoard[location.row][location.col] = Cell(
location: location,
blockId: cell.blockId,
value: cell.value,
isFixed: false,
);
}
cells = transformedBoard;
}
Cell get(CellLocation location) {
if (location.row < cells.length) {
if (location.col < cells[location.row].length) {
return cells[location.row][location.col];
}
}
return Cell.none;
}
void setCell(CellLocation location, Cell cell) {
cells[location.row][location.col] = cell;
}
void setValue(CellLocation location, int value) {
Cell currentCell = get(location);
setCell(
location,
Cell(
blockId: currentCell.blockId,
isFixed: currentCell.isFixed,
location: location,
value: value,
));
}
List<String> getBlockIds() {
List<String> blockIds = [];
for (CellLocation location in getCellLocations()) {
final String blockId = get(location).blockId;
if (!blockIds.contains(blockId)) {
blockIds.add(blockId);
}
}
return blockIds;
}
List<int> getValuesInBlock(String blockId) {
final List<int> values = [];
for (CellLocation location in getCellLocations()) {
if (get(location).blockId == blockId) {
values.add(get(location).value);
}
}
return values;
}
BoardCells copyCells() {
final BoardCells copiedGrid = [];
for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
final List<Cell> row = [];
for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
final Cell cell = cells[rowIndex][colIndex];
row.add(Cell(
location: CellLocation.go(rowIndex, colIndex),
blockId: cell.blockId,
value: cell.value,
isFixed: false,
));
}
copiedGrid.add(row);
}
return copiedGrid;
}
resolve() {
final Board solvedBoard = Board(cells: copyCells(), solvedCells: []);
do {
// last cell in blocks
final List<List<int>> cellsLastEmptyInBlock = solvedBoard.getLastEmptyCellsInBlocks();
for (var cellData in cellsLastEmptyInBlock) {
solvedBoard.setValue(CellLocation.go(cellData[0], cellData[1]), cellData[2]);
}
// last cell in blocks
final List<List<int>> cellsSingleInBlockWithoutConflict =
solvedBoard.getOnlyCellInBlockWithoutConflict();
for (var cellData in cellsSingleInBlockWithoutConflict) {
solvedBoard.setValue(CellLocation.go(cellData[0], cellData[1]), cellData[2]);
}
// empty cells with unique available value
final List<List<int>> cellsWithUniqueAvailableValue =
solvedBoard.getEmptyCellsWithUniqueAvailableValue();
for (var cellData in cellsWithUniqueAvailableValue) {
solvedBoard.setValue(CellLocation.go(cellData[0], cellData[1]), cellData[2]);
}
// no more empty cell to fill
if (cellsLastEmptyInBlock.isEmpty && cellsWithUniqueAvailableValue.isEmpty) {
if (solvedBoard.isSolved()) {
printlog('ok compute solved board');
} else {
printlog('!!');
printlog('!! failed to resolve board');
printlog('!!');
}
break;
}
} while (true);
solvedCells = solvedBoard.cells;
}
bool isSolved() {
// check grid is fully completed
for (CellLocation location in getCellLocations()) {
if (get(location).value == 0) {
return false;
}
}
// check each block contains all values from 1 to block size
for (String blockId in getBlockIds()) {
List<int> values = [];
List<int> duplicateValues = [];
for (CellLocation location in getCellLocations()) {
if (get(location).blockId == blockId) {
final int value = get(location).value;
if (value != 0) {
if (!values.contains(value)) {
values.add(value);
} else {
duplicateValues.add(value);
}
}
}
}
for (int duplicateValue in duplicateValues) {
for (CellLocation location in getCellLocations()) {
if (get(location).blockId == blockId && get(location).value == duplicateValue) {
return false;
}
}
}
}
return true;
}
int getMaxValueForBlock(String? blockId) {
int maxValue = 0;
for (CellLocation location in getCellLocations()) {
if (get(location).blockId == blockId) {
maxValue++;
}
}
return maxValue;
}
List<int> getMissingValuesInBlock(String blockId) {
List<int> missingValues = [];
final List<int> values = getValuesInBlock(blockId);
final List<int> expectedValues =
List<int>.generate(getMaxValueForBlock(blockId), (i) => i + 1);
for (int candidateValue in expectedValues) {
if (!values.contains(candidateValue)) {
missingValues.add(candidateValue);
}
}
return missingValues;
}
List<List<int>> getLastEmptyCellsInBlocks() {
List<List<int>> candidateCells = [];
for (CellLocation location in getCellLocations()) {
final Cell cell = get(location);
if (cell.value == 0) {
final int blockSize = getMaxValueForBlock(cell.blockId);
final List<int> blockValues = getValuesInBlock(cell.blockId);
blockValues.removeWhere((value) => value == 0);
if (blockValues.length == blockSize - 1) {
int candidateValue = 0;
for (int value = 1; value <= blockSize; value++) {
if (!blockValues.contains(value)) {
candidateValue = value;
}
}
candidateCells.add([location.row, location.col, candidateValue]);
}
}
}
return candidateCells;
}
List<List<int>> getOnlyCellInBlockWithoutConflict() {
List<List<int>> candidateCells = [];
for (String blockId in getBlockIds()) {
List<int> missingValuesInBlock = getMissingValuesInBlock(blockId);
for (int candidateValue in missingValuesInBlock) {
final List<CellLocation> allowedCellsForThisValue = [];
for (CellLocation location in getCellLocations(blockId)) {
if (get(location).value == 0) {
if (isValueAllowed(location, candidateValue)) {
allowedCellsForThisValue.add(location);
}
}
}
if (allowedCellsForThisValue.length == 1) {
final CellLocation candidateLocation = allowedCellsForThisValue[0];
candidateCells.add([candidateLocation.row, candidateLocation.col, candidateValue]);
}
}
}
return candidateCells;
}
List<List<int>> getEmptyCellsWithUniqueAvailableValue() {
List<List<int>> candidateCells = [];
for (CellLocation location in getCellLocations()) {
if (get(location).value == 0) {
int allowedValuesCount = 0;
int candidateValue = 0;
final int maxValueForThisCell = getMaxValueForBlock(get(location).blockId);
for (int value = 1; value <= maxValueForThisCell; value++) {
if (isValueAllowed(location, value)) {
candidateValue = value;
allowedValuesCount++;
}
}
if (allowedValuesCount == 1) {
candidateCells.add([location.row, location.col, candidateValue]);
}
}
}
return candidateCells;
}
bool blockContainsDuplicates(String blockId, [int? candidateValue]) {
List<int> duplicateValues = [];
List<int> values = [];
if (candidateValue != null) {
values.add(candidateValue);
}
for (CellLocation location in getCellLocations(blockId)) {
final int value = get(location).value;
if (value != 0) {
if (!values.contains(value)) {
values.add(value);
} else {
duplicateValues.add(value);
}
}
}
return duplicateValues.isNotEmpty;
}
bool boardHasSiblingWithSameValue() {
for (CellLocation location in getCellLocations()) {
final int value = get(location).value;
if (value != 0 && cellHasSiblingWithSameValue(location)) {
return true;
}
}
return false;
}
bool cellHasSiblingWithSameValue(CellLocation cellLocation, [int? candidateValue]) {
if (cells.isEmpty) {
return false;
}
final int boardSizeVertical = cells.length;
final int boardSizeHorizontal = cells[0].length;
final int value = candidateValue ?? get(cellLocation).value;
if (value != 0) {
for (int deltaRow in [-1, 0, 1]) {
for (int deltaCol in [-1, 0, 1]) {
if (cellLocation.row + deltaRow >= 0 &&
cellLocation.row + deltaRow < boardSizeHorizontal &&
cellLocation.col + deltaCol >= 0 &&
cellLocation.col + deltaCol < boardSizeVertical &&
!(deltaRow == 0 && deltaCol == 0)) {
final CellLocation candidateLocation =
CellLocation.go(cellLocation.row + deltaRow, cellLocation.col + deltaCol);
final int siblingValue = get(candidateLocation).value;
if (siblingValue == value) {
return true;
}
}
}
}
}
return false;
}
bool isValueAllowed(CellLocation? location, int value) {
if ((location == null) || (value == 0)) {
return true;
}
// check siblings
if (cellHasSiblingWithSameValue(location, value)) {
return false;
}
// check block does not contain duplicates
if (blockContainsDuplicates(get(location).blockId, value)) {
return false;
}
return true;
}
void dump() {
const String stringValues = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
printlog('');
printlog('- blocks / values / solved -');
printlog('-------');
if (cells.isEmpty) {
printlog('empty board');
} else {
for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
String rowBlocks = '';
String rowValues = '';
String rowSolved = '';
for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
rowBlocks += cells[rowIndex][colIndex].blockId;
rowValues += stringValues[cells[rowIndex][colIndex].value];
if (solvedCells.isEmpty) {
rowSolved += '*';
} else {
rowSolved += stringValues[solvedCells[rowIndex][colIndex].value];
}
}
printlog('$rowBlocks | $rowValues | $rowSolved');
}
}
printlog('-------');
printlog('');
}
@override
String toString() {
return '$Board(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'cells': cells,
};
}
}
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/models/activity/cell_location.dart';
class Cell {
const Cell({
required this.location,
required this.blockId,
required this.value,
required this.isFixed,
});
final CellLocation location;
final String blockId;
final int value;
final bool isFixed;
static Cell none = Cell(
location: CellLocation.go(0, 0),
blockId: '',
value: 0,
isFixed: true,
);
void dump() {
printlog('$Cell:');
printlog(' location: $location');
printlog(' blockId: $blockId');
printlog(' value: $value');
printlog(' isFixed: $isFixed');
printlog('');
}
@override
String toString() {
return '$Cell(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'location': location.toJson(),
'value': value,
'isFixed': isFixed,
};
}
}
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class CellLocation {
final int col;
final int row;
CellLocation({
required this.col,
required this.row,
});
factory CellLocation.go(int row, int col) {
return CellLocation(col: col, row: row);
}
void dump() {
printlog('$CellLocation:');
printlog(' row: $row');
printlog(' col: $col');
printlog('');
}
@override
String toString() {
return '[${col + 1},${row + 1}]';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'row': row,
'col': col,
};
}
}
import 'package:suguru/models/activity/cell.dart';
typedef BoardCells = List<List<Cell>>;
typedef ConflictsCount = List<List<int>>;
typedef AnimatedBoard = List<List<bool>>;
typedef AnimatedBoardSequence = List<AnimatedBoard>;
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/models/activity/activity.dart';
import 'package:suguru/ui/widgets/game/bar_select_cell_value.dart';
class GameBottomWidget extends StatelessWidget {
const GameBottomWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<ActivityCubit, ActivityState>(
builder: (BuildContext context, ActivityState activityState) {
final Activity currentActivity = activityState.currentActivity;
return currentActivity.isFinished
? const SizedBox.shrink()
: const SelectCellValueBar();
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/config/application_config.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/models/activity/activity.dart';
class GameEndWidget extends StatelessWidget {
const GameEndWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<ActivityCubit, ActivityState>(
builder: (BuildContext context, ActivityState activityState) {
final Activity currentActivity = activityState.currentActivity;
final Image decorationImage = Image(
image: AssetImage(
currentActivity.gameWon ? 'assets/ui/game_win.png' : 'assets/ui/game_fail.png'),
fit: BoxFit.fill,
);
final double width = MediaQuery.of(context).size.width;
return Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2),
child: Table(
defaultColumnWidth: FixedColumnWidth(width / 3.1),
defaultVerticalAlignment: TableCellVerticalAlignment.bottom,
children: [
TableRow(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [decorationImage],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
currentActivity.animationInProgress == true
? decorationImage
: ActivityButtonQuit(
onPressed: () {
BlocProvider.of<ActivityCubit>(context).quitActivity();
BlocProvider.of<NavCubitPage>(context)
.updateIndex(ApplicationConfig.activityPageIndexHome);
},
color: Colors.blue,
),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [decorationImage],
),
],
),
],
),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/models/activity/activity.dart';
import 'package:suguru/ui/widgets/game/button_show_conflicts.dart';
import 'package:suguru/ui/widgets/game/button_show_tip.dart';
class GameTopWidget extends StatelessWidget {
const GameTopWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<ActivityCubit, ActivityState>(
builder: (BuildContext context, ActivityState activityState) {
final Activity currentActivity = activityState.currentActivity;
return SizedBox(
height: 60,
child: (currentActivity.isRunning && !currentActivity.isFinished)
? const Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ButtonShowTip(),
ButtonShowConflicts(),
],
)
: const SizedBox.shrink(),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/models/activity/activity.dart';
import 'package:suguru/ui/game/game_bottom.dart';
import 'package:suguru/ui/game/game_end.dart';
import 'package:suguru/ui/game/game_top.dart';
import 'package:suguru/ui/widgets/game/game_board.dart';
class PageGame extends StatelessWidget {
const PageGame({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<ActivityCubit, ActivityState>(
builder: (BuildContext context, ActivityState activityState) {
final Activity currentActivity = activityState.currentActivity;
return Container(
alignment: AlignmentDirectional.topCenter,
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const GameTopWidget(),
const SizedBox(height: 8),
const GameBoardWidget(),
const SizedBox(height: 8),
const GameBottomWidget(),
// StyledButton.text(
// onPressed: () => currentActivity.checkAllTemplates(),
// caption: '[debug] test all templates',
// color: Colors.red,
// ),
const Expanded(child: SizedBox.shrink()),
currentActivity.isFinished ? const GameEndWidget() : const SizedBox.shrink(),
],
),
);
},
);
}
}
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/config/application_config.dart';
class ParameterPainterBoardSize extends CustomPainter {
const ParameterPainterBoardSize({
required this.context,
required this.value,
});
final BuildContext context;
final String value;
@override
void paint(Canvas canvas, Size size) {
// force square
final double canvasSize = min(size.width, size.height);
int gridWidth = 1;
int gridHeight = 1;
switch (value) {
case ApplicationConfig.boardSizeValueSmall:
gridWidth = 2;
gridHeight = 2;
break;
case ApplicationConfig.boardSizeValueMedium:
gridWidth = 3;
gridHeight = 3;
break;
case ApplicationConfig.boardSizeValueLarge:
gridWidth = 4;
gridHeight = 4;
break;
default:
printlog('Wrong value for size parameter value: $value');
}
final paint = Paint();
paint.strokeJoin = StrokeJoin.round;
paint.strokeWidth = 3;
// Mini grid
final borderColor = Colors.grey.shade800;
final double cellSize = canvasSize / 5;
final double originX = (canvasSize - gridWidth * cellSize) / 2;
final double originY = (canvasSize - gridHeight * cellSize) / 2;
for (int row = 0; row < gridHeight; row++) {
for (int col = 0; col < gridWidth; col++) {
final Offset topLeft = Offset(originX + col * cellSize, originY + row * cellSize);
final Offset bottomRight = topLeft + Offset(cellSize, cellSize);
paint.color = Colors.white;
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);
}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/config/application_config.dart';
class ParameterPainterDifficultyLevel extends CustomPainter {
const ParameterPainterDifficultyLevel({
required this.context,
required this.value,
});
final BuildContext context;
final String value;
@override
void paint(Canvas canvas, Size size) {
// force square
final double canvasSize = min(size.width, size.height);
final List<dynamic> stars = [];
switch (value) {
case ApplicationConfig.difficultyLevelValueEasy:
stars.add([0.5, 0.5]);
break;
case ApplicationConfig.difficultyLevelValueMedium:
stars.add([0.3, 0.5]);
stars.add([0.7, 0.5]);
break;
case ApplicationConfig.difficultyLevelValueHard:
stars.add([0.3, 0.3]);
stars.add([0.7, 0.3]);
stars.add([0.5, 0.7]);
break;
default:
printlog('Wrong value for level parameter value: $value');
}
final paint = Paint();
paint.strokeJoin = StrokeJoin.round;
paint.strokeWidth = 3 / 100 * canvasSize;
// Stars
final textSpan = TextSpan(
text: '⭐',
style: TextStyle(
color: Colors.black,
fontSize: canvasSize / 3,
fontWeight: FontWeight.bold,
),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
);
textPainter.layout();
for (var center in stars) {
textPainter.paint(
canvas,
Offset(
canvasSize * center[0] - textPainter.width * 0.5,
canvasSize * center[1] - textPainter.height * 0.5,
),
);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:suguru/config/application_config.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/models/activity/activity.dart';
class SkeletonScreen extends StatelessWidget {
const SkeletonScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<NavCubitScreen, int>(
builder: (BuildContext context, int screenIndex) {
return BlocBuilder<ActivityCubit, ActivityState>(
builder: (BuildContext context, ActivityState activityState) {
return BlocBuilder<NavCubitScreen, int>(
builder: (BuildContext context, int pageIndex) {
final Activity currentActivity = activityState.currentActivity;
return Scaffold(
appBar: GlobalAppBar(
appConfig: ApplicationConfig.config,
pageIndex: pageIndex,
isActivityRunning:
currentActivity.isRunning && !currentActivity.isFinished,
),
extendBodyBehindAppBar: false,
body: Material(
color: Theme.of(context).colorScheme.surface,
child: Padding(
padding: const EdgeInsets.only(
top: 8,
left: 2,
right: 2,
),
child: ApplicationConfig.config.navigation.getScreenWidget(
appConfig: ApplicationConfig.config,
screenIndex: screenIndex,
),
),
),
backgroundColor: Theme.of(context).colorScheme.surface,
bottomNavigationBar: ApplicationConfig.config.navigation.displayBottomNavBar
? BottomNavBar(appConfig: ApplicationConfig.config)
: null,
);
},
);
},
);
},
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment