From 08ffe708b98f38fedf4a1bac80ba34740be29ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Tue, 14 May 2024 16:58:35 +0200 Subject: [PATCH] Normalize game architecture --- .editorconfig | 3 + android/gradle.properties | 4 +- assets/icons/.gitkeep | 0 assets/skins/.gitkeep | 0 assets/translations/en.json | 8 +- assets/translations/fr.json | 8 +- assets/{icons => ui}/button_back.png | Bin .../button_delete_saved_game.png | Bin assets/{icons => ui}/button_help.png | Bin assets/{icons => ui}/button_resume_game.png | Bin .../{icons => ui}/button_show_conflicts.png | Bin assets/{icons => ui}/button_start.png | Bin assets/{icons => ui}/cell_empty.png | Bin assets/{icons => ui}/game_win.png | Bin assets/{icons => ui}/placeholder.png | Bin assets/{icons => ui}/skin_digits.png | Bin assets/{icons => ui}/skin_food.png | Bin assets/{icons => ui}/skin_monsters.png | Bin assets/{icons => ui}/skin_nature.png | Bin .../metadata/android/en-US/changelogs/72.txt | 1 + .../metadata/android/fr-FR/changelogs/72.txt | 1 + lib/config/default_game_settings.dart | 18 +- lib/config/default_global_settings.dart | 14 +- lib/config/menu.dart | 17 +- lib/config/theme.dart | 10 +- lib/cubit/game_cubit.dart | 116 ++++++----- lib/cubit/game_state.dart | 10 +- lib/cubit/settings_game_cubit.dart | 5 +- lib/cubit/settings_game_state.dart | 4 - lib/cubit/settings_global_cubit.dart | 4 +- lib/cubit/settings_global_state.dart | 4 - .../grids.dart => data/game_data.dart} | 2 +- lib/main.dart | 34 +-- lib/models/{ => game}/board.dart | 145 ++++++++++++- lib/models/{ => game}/cell.dart | 2 +- lib/models/{ => game}/cell_location.dart | 0 lib/models/{ => game}/game.dart | 196 ++++++++++-------- lib/models/{ => game}/types.dart | 2 +- lib/models/{ => settings}/settings_game.dart | 4 +- .../{ => settings}/settings_global.dart | 0 lib/ui/game/game_bottom.dart | 21 ++ .../game_end.dart} | 23 +- lib/ui/game/game_top.dart | 34 +++ lib/ui/helpers/app_titles.dart | 32 +++ lib/ui/helpers/outlined_text_widget.dart | 51 +++++ .../game.dart => layouts/game_layout.dart} | 23 +- .../parameters_layout.dart} | 93 +++++---- .../parameter_image.dart | 2 +- .../parameter_painter.dart | 12 +- lib/ui/screens/game_page.dart | 26 --- .../{about_page.dart => page_about.dart} | 8 +- lib/ui/screens/page_game.dart | 24 +++ ...{settings_page.dart => page_settings.dart} | 10 +- .../{widgets => }/settings/settings_form.dart | 2 +- lib/ui/{widgets => }/settings/theme_card.dart | 0 lib/ui/skeleton.dart | 20 +- .../actions/button_delete_saved_game.dart | 21 ++ lib/ui/widgets/actions/button_game_quit.dart | 21 ++ .../{ => actions}/button_game_start_new.dart | 14 +- .../actions/button_resume_saved_game.dart | 21 ++ lib/ui/widgets/button_delete_saved_game.dart | 27 --- lib/ui/widgets/button_game_restart.dart | 27 --- lib/ui/widgets/button_resume_saved_game.dart | 27 --- .../{ => game}/bar_select_cell_value.dart | 10 +- .../widgets/game/button_show_conflicts.dart | 37 ++++ lib/ui/widgets/game/button_show_tip.dart | 60 ++++++ .../{cell_widget.dart => game/cell.dart} | 8 +- .../cell_update.dart} | 8 +- .../game/game_board.dart} | 21 +- lib/ui/widgets/global_app_bar.dart | 73 +------ lib/ui/widgets/header_app.dart | 24 --- lib/utils/board_animate.dart | 6 +- lib/utils/board_utils.dart | 147 ------------- lib/utils/color_extensions.dart | 33 +++ pubspec.lock | 52 ++--- pubspec.yaml | 12 +- .../app/build_application_resources.sh | 2 +- {icons => resources/app}/featureGraphic.svg | 0 {icons => resources/app}/icon.svg | 0 resources/build_resources.sh | 7 + .../ui/build_ui_resources.sh | 91 +++----- .../ui/images}/button_back.svg | 0 .../ui/images}/button_delete_saved_game.svg | 0 .../ui/images}/button_help.svg | 0 .../ui/images}/button_resume_game.svg | 0 .../ui/images}/button_show_conflicts.svg | 0 .../ui/images}/button_start.svg | 0 {icons => resources/ui/images}/cell_empty.svg | 0 {icons => resources/ui/images}/game_win.svg | 0 .../ui/images}/placeholder.svg | 0 .../ui/images}/skin_digits.svg | 0 {icons => resources/ui/images}/skin_food.svg | 0 .../ui/images}/skin_monsters.svg | 0 .../ui/images}/skin_nature.svg | 0 {icons => resources/ui}/skins/digits/1.svg | 0 {icons => resources/ui}/skins/digits/10.svg | 0 {icons => resources/ui}/skins/digits/11.svg | 0 {icons => resources/ui}/skins/digits/12.svg | 0 {icons => resources/ui}/skins/digits/13.svg | 0 {icons => resources/ui}/skins/digits/14.svg | 0 {icons => resources/ui}/skins/digits/15.svg | 0 {icons => resources/ui}/skins/digits/16.svg | 0 {icons => resources/ui}/skins/digits/2.svg | 0 {icons => resources/ui}/skins/digits/3.svg | 0 {icons => resources/ui}/skins/digits/4.svg | 0 {icons => resources/ui}/skins/digits/5.svg | 0 {icons => resources/ui}/skins/digits/6.svg | 0 {icons => resources/ui}/skins/digits/7.svg | 0 {icons => resources/ui}/skins/digits/8.svg | 0 {icons => resources/ui}/skins/digits/9.svg | 0 {icons => resources/ui}/skins/food/1.svg | 0 {icons => resources/ui}/skins/food/10.svg | 0 {icons => resources/ui}/skins/food/11.svg | 0 {icons => resources/ui}/skins/food/12.svg | 0 {icons => resources/ui}/skins/food/13.svg | 0 {icons => resources/ui}/skins/food/14.svg | 0 {icons => resources/ui}/skins/food/15.svg | 0 {icons => resources/ui}/skins/food/16.svg | 0 {icons => resources/ui}/skins/food/2.svg | 0 {icons => resources/ui}/skins/food/3.svg | 0 {icons => resources/ui}/skins/food/4.svg | 0 {icons => resources/ui}/skins/food/5.svg | 0 {icons => resources/ui}/skins/food/6.svg | 0 {icons => resources/ui}/skins/food/7.svg | 0 {icons => resources/ui}/skins/food/8.svg | 0 {icons => resources/ui}/skins/food/9.svg | 0 {icons => resources/ui}/skins/monsters/1.svg | 0 {icons => resources/ui}/skins/monsters/10.svg | 0 {icons => resources/ui}/skins/monsters/11.svg | 0 {icons => resources/ui}/skins/monsters/12.svg | 0 {icons => resources/ui}/skins/monsters/13.svg | 0 {icons => resources/ui}/skins/monsters/14.svg | 0 {icons => resources/ui}/skins/monsters/15.svg | 0 {icons => resources/ui}/skins/monsters/16.svg | 0 {icons => resources/ui}/skins/monsters/2.svg | 0 {icons => resources/ui}/skins/monsters/3.svg | 0 {icons => resources/ui}/skins/monsters/4.svg | 0 {icons => resources/ui}/skins/monsters/5.svg | 0 {icons => resources/ui}/skins/monsters/6.svg | 0 {icons => resources/ui}/skins/monsters/7.svg | 0 {icons => resources/ui}/skins/monsters/8.svg | 0 {icons => resources/ui}/skins/monsters/9.svg | 0 {icons => resources/ui}/skins/nature/1.svg | 0 {icons => resources/ui}/skins/nature/10.svg | 0 {icons => resources/ui}/skins/nature/11.svg | 0 {icons => resources/ui}/skins/nature/12.svg | 0 {icons => resources/ui}/skins/nature/13.svg | 0 {icons => resources/ui}/skins/nature/14.svg | 0 {icons => resources/ui}/skins/nature/15.svg | 0 {icons => resources/ui}/skins/nature/16.svg | 0 {icons => resources/ui}/skins/nature/2.svg | 0 {icons => resources/ui}/skins/nature/3.svg | 0 {icons => resources/ui}/skins/nature/4.svg | 0 {icons => resources/ui}/skins/nature/5.svg | 0 {icons => resources/ui}/skins/nature/6.svg | 0 {icons => resources/ui}/skins/nature/7.svg | 0 {icons => resources/ui}/skins/nature/8.svg | 0 {icons => resources/ui}/skins/nature/9.svg | 0 158 files changed, 970 insertions(+), 772 deletions(-) delete mode 100644 assets/icons/.gitkeep delete mode 100644 assets/skins/.gitkeep rename assets/{icons => ui}/button_back.png (100%) rename assets/{icons => ui}/button_delete_saved_game.png (100%) rename assets/{icons => ui}/button_help.png (100%) rename assets/{icons => ui}/button_resume_game.png (100%) rename assets/{icons => ui}/button_show_conflicts.png (100%) rename assets/{icons => ui}/button_start.png (100%) rename assets/{icons => ui}/cell_empty.png (100%) rename assets/{icons => ui}/game_win.png (100%) rename assets/{icons => ui}/placeholder.png (100%) rename assets/{icons => ui}/skin_digits.png (100%) rename assets/{icons => ui}/skin_food.png (100%) rename assets/{icons => ui}/skin_monsters.png (100%) rename assets/{icons => ui}/skin_nature.png (100%) create mode 100644 fastlane/metadata/android/en-US/changelogs/72.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/72.txt rename lib/{assets/grids.dart => data/game_data.dart} (99%) rename lib/models/{ => game}/board.dart (54%) rename lib/models/{ => game}/cell.dart (92%) rename lib/models/{ => game}/cell_location.dart (100%) rename lib/models/{ => game}/game.dart (82%) rename lib/models/{ => game}/types.dart (79%) rename lib/models/{ => settings}/settings_game.dart (96%) rename lib/models/{ => settings}/settings_global.dart (100%) create mode 100644 lib/ui/game/game_bottom.dart rename lib/ui/{widgets/message_game_end.dart => game/game_end.dart} (63%) create mode 100644 lib/ui/game/game_top.dart create mode 100644 lib/ui/helpers/app_titles.dart create mode 100644 lib/ui/helpers/outlined_text_widget.dart rename lib/ui/{widgets/game.dart => layouts/game_layout.dart} (53%) rename lib/ui/{widgets/parameters.dart => layouts/parameters_layout.dart} (67%) rename lib/ui/{widgets => parameters}/parameter_image.dart (94%) rename lib/ui/{painters => parameters}/parameter_painter.dart (95%) delete mode 100644 lib/ui/screens/game_page.dart rename lib/ui/screens/{about_page.dart => page_about.dart} (86%) create mode 100644 lib/ui/screens/page_game.dart rename lib/ui/screens/{settings_page.dart => page_settings.dart} (66%) rename lib/ui/{widgets => }/settings/settings_form.dart (96%) rename lib/ui/{widgets => }/settings/theme_card.dart (100%) create mode 100644 lib/ui/widgets/actions/button_delete_saved_game.dart create mode 100644 lib/ui/widgets/actions/button_game_quit.dart rename lib/ui/widgets/{ => actions}/button_game_start_new.dart (72%) create mode 100644 lib/ui/widgets/actions/button_resume_saved_game.dart delete mode 100644 lib/ui/widgets/button_delete_saved_game.dart delete mode 100644 lib/ui/widgets/button_game_restart.dart delete mode 100644 lib/ui/widgets/button_resume_saved_game.dart rename lib/ui/widgets/{ => game}/bar_select_cell_value.dart (88%) create mode 100644 lib/ui/widgets/game/button_show_conflicts.dart create mode 100644 lib/ui/widgets/game/button_show_tip.dart rename lib/ui/widgets/{cell_widget.dart => game/cell.dart} (96%) rename lib/ui/widgets/{cell_widget_update.dart => game/cell_update.dart} (91%) rename lib/ui/{layout/board.dart => widgets/game/game_board.dart} (68%) delete mode 100644 lib/ui/widgets/header_app.dart delete mode 100644 lib/utils/board_utils.dart create mode 100644 lib/utils/color_extensions.dart rename icons/build_application_icons.sh => resources/app/build_application_resources.sh (98%) rename {icons => resources/app}/featureGraphic.svg (100%) rename {icons => resources/app}/icon.svg (100%) create mode 100755 resources/build_resources.sh rename icons/build_game_icons.sh => resources/ui/build_ui_resources.sh (54%) rename {icons => resources/ui/images}/button_back.svg (100%) rename {icons => resources/ui/images}/button_delete_saved_game.svg (100%) rename {icons => resources/ui/images}/button_help.svg (100%) rename {icons => resources/ui/images}/button_resume_game.svg (100%) rename {icons => resources/ui/images}/button_show_conflicts.svg (100%) rename {icons => resources/ui/images}/button_start.svg (100%) rename {icons => resources/ui/images}/cell_empty.svg (100%) rename {icons => resources/ui/images}/game_win.svg (100%) rename {icons => resources/ui/images}/placeholder.svg (100%) rename {icons => resources/ui/images}/skin_digits.svg (100%) rename {icons => resources/ui/images}/skin_food.svg (100%) rename {icons => resources/ui/images}/skin_monsters.svg (100%) rename {icons => resources/ui/images}/skin_nature.svg (100%) rename {icons => resources/ui}/skins/digits/1.svg (100%) rename {icons => resources/ui}/skins/digits/10.svg (100%) rename {icons => resources/ui}/skins/digits/11.svg (100%) rename {icons => resources/ui}/skins/digits/12.svg (100%) rename {icons => resources/ui}/skins/digits/13.svg (100%) rename {icons => resources/ui}/skins/digits/14.svg (100%) rename {icons => resources/ui}/skins/digits/15.svg (100%) rename {icons => resources/ui}/skins/digits/16.svg (100%) rename {icons => resources/ui}/skins/digits/2.svg (100%) rename {icons => resources/ui}/skins/digits/3.svg (100%) rename {icons => resources/ui}/skins/digits/4.svg (100%) rename {icons => resources/ui}/skins/digits/5.svg (100%) rename {icons => resources/ui}/skins/digits/6.svg (100%) rename {icons => resources/ui}/skins/digits/7.svg (100%) rename {icons => resources/ui}/skins/digits/8.svg (100%) rename {icons => resources/ui}/skins/digits/9.svg (100%) rename {icons => resources/ui}/skins/food/1.svg (100%) rename {icons => resources/ui}/skins/food/10.svg (100%) rename {icons => resources/ui}/skins/food/11.svg (100%) rename {icons => resources/ui}/skins/food/12.svg (100%) rename {icons => resources/ui}/skins/food/13.svg (100%) rename {icons => resources/ui}/skins/food/14.svg (100%) rename {icons => resources/ui}/skins/food/15.svg (100%) rename {icons => resources/ui}/skins/food/16.svg (100%) rename {icons => resources/ui}/skins/food/2.svg (100%) rename {icons => resources/ui}/skins/food/3.svg (100%) rename {icons => resources/ui}/skins/food/4.svg (100%) rename {icons => resources/ui}/skins/food/5.svg (100%) rename {icons => resources/ui}/skins/food/6.svg (100%) rename {icons => resources/ui}/skins/food/7.svg (100%) rename {icons => resources/ui}/skins/food/8.svg (100%) rename {icons => resources/ui}/skins/food/9.svg (100%) rename {icons => resources/ui}/skins/monsters/1.svg (100%) rename {icons => resources/ui}/skins/monsters/10.svg (100%) rename {icons => resources/ui}/skins/monsters/11.svg (100%) rename {icons => resources/ui}/skins/monsters/12.svg (100%) rename {icons => resources/ui}/skins/monsters/13.svg (100%) rename {icons => resources/ui}/skins/monsters/14.svg (100%) rename {icons => resources/ui}/skins/monsters/15.svg (100%) rename {icons => resources/ui}/skins/monsters/16.svg (100%) rename {icons => resources/ui}/skins/monsters/2.svg (100%) rename {icons => resources/ui}/skins/monsters/3.svg (100%) rename {icons => resources/ui}/skins/monsters/4.svg (100%) rename {icons => resources/ui}/skins/monsters/5.svg (100%) rename {icons => resources/ui}/skins/monsters/6.svg (100%) rename {icons => resources/ui}/skins/monsters/7.svg (100%) rename {icons => resources/ui}/skins/monsters/8.svg (100%) rename {icons => resources/ui}/skins/monsters/9.svg (100%) rename {icons => resources/ui}/skins/nature/1.svg (100%) rename {icons => resources/ui}/skins/nature/10.svg (100%) rename {icons => resources/ui}/skins/nature/11.svg (100%) rename {icons => resources/ui}/skins/nature/12.svg (100%) rename {icons => resources/ui}/skins/nature/13.svg (100%) rename {icons => resources/ui}/skins/nature/14.svg (100%) rename {icons => resources/ui}/skins/nature/15.svg (100%) rename {icons => resources/ui}/skins/nature/16.svg (100%) rename {icons => resources/ui}/skins/nature/2.svg (100%) rename {icons => resources/ui}/skins/nature/3.svg (100%) rename {icons => resources/ui}/skins/nature/4.svg (100%) rename {icons => resources/ui}/skins/nature/5.svg (100%) rename {icons => resources/ui}/skins/nature/6.svg (100%) rename {icons => resources/ui}/skins/nature/7.svg (100%) rename {icons => resources/ui}/skins/nature/8.svg (100%) rename {icons => resources/ui}/skins/nature/9.svg (100%) diff --git a/.editorconfig b/.editorconfig index 779f99a..8d86e45 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,8 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +[*.json] +indent_size = 2 + [*.md] trim_trailing_whitespace = false diff --git a/android/gradle.properties b/android/gradle.properties index de61e66..aa6a055 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.1.22 -app.versionCode=71 +app.versionName=0.2.0 +app.versionCode=72 diff --git a/assets/icons/.gitkeep b/assets/icons/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/assets/skins/.gitkeep b/assets/skins/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/assets/translations/en.json b/assets/translations/en.json index fc87804..698056d 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,14 +1,12 @@ { "app_name": "Sudoku", - "bottom_nav_game": "Game", - "bottom_nav_settings": "Settings", - "bottom_nav_about": "About", - "settings_title": "Settings", "settings_label_theme": "Theme mode", "about_title": "About", "about_content": "Simple Sudoku Game. Easy to play, easy to enjoy.", - "about_version": "Version: {version}" + "about_version": "Version: {version}", + + "": "" } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 64b3b73..7d1f4e8 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,14 +1,12 @@ { "app_name": "Sudoku", - "bottom_nav_game": "Jeu", - "bottom_nav_settings": "Réglages", - "bottom_nav_about": "Infos", - "settings_title": "Réglages", "settings_label_theme": "Thème de couleurs", "about_title": "Informations", "about_content": "Jeu de Sudoku simple, facile à jouer, facile à apprécier.", - "about_version": "Version : {version}" + "about_version": "Version : {version}", + + "": "" } diff --git a/assets/icons/button_back.png b/assets/ui/button_back.png similarity index 100% rename from assets/icons/button_back.png rename to assets/ui/button_back.png diff --git a/assets/icons/button_delete_saved_game.png b/assets/ui/button_delete_saved_game.png similarity index 100% rename from assets/icons/button_delete_saved_game.png rename to assets/ui/button_delete_saved_game.png diff --git a/assets/icons/button_help.png b/assets/ui/button_help.png similarity index 100% rename from assets/icons/button_help.png rename to assets/ui/button_help.png diff --git a/assets/icons/button_resume_game.png b/assets/ui/button_resume_game.png similarity index 100% rename from assets/icons/button_resume_game.png rename to assets/ui/button_resume_game.png diff --git a/assets/icons/button_show_conflicts.png b/assets/ui/button_show_conflicts.png similarity index 100% rename from assets/icons/button_show_conflicts.png rename to assets/ui/button_show_conflicts.png diff --git a/assets/icons/button_start.png b/assets/ui/button_start.png similarity index 100% rename from assets/icons/button_start.png rename to assets/ui/button_start.png diff --git a/assets/icons/cell_empty.png b/assets/ui/cell_empty.png similarity index 100% rename from assets/icons/cell_empty.png rename to assets/ui/cell_empty.png diff --git a/assets/icons/game_win.png b/assets/ui/game_win.png similarity index 100% rename from assets/icons/game_win.png rename to assets/ui/game_win.png diff --git a/assets/icons/placeholder.png b/assets/ui/placeholder.png similarity index 100% rename from assets/icons/placeholder.png rename to assets/ui/placeholder.png diff --git a/assets/icons/skin_digits.png b/assets/ui/skin_digits.png similarity index 100% rename from assets/icons/skin_digits.png rename to assets/ui/skin_digits.png diff --git a/assets/icons/skin_food.png b/assets/ui/skin_food.png similarity index 100% rename from assets/icons/skin_food.png rename to assets/ui/skin_food.png diff --git a/assets/icons/skin_monsters.png b/assets/ui/skin_monsters.png similarity index 100% rename from assets/icons/skin_monsters.png rename to assets/ui/skin_monsters.png diff --git a/assets/icons/skin_nature.png b/assets/ui/skin_nature.png similarity index 100% rename from assets/icons/skin_nature.png rename to assets/ui/skin_nature.png diff --git a/fastlane/metadata/android/en-US/changelogs/72.txt b/fastlane/metadata/android/en-US/changelogs/72.txt new file mode 100644 index 0000000..d4afd51 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/72.txt @@ -0,0 +1 @@ +Improve/normalize game architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/72.txt b/fastlane/metadata/android/fr-FR/changelogs/72.txt new file mode 100644 index 0000000..6a9871a --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/72.txt @@ -0,0 +1 @@ +Amélioration/normalisation de l'architecture du jeu. diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart index 2f9ae75..15e6d1b 100644 --- a/lib/config/default_game_settings.dart +++ b/lib/config/default_game_settings.dart @@ -1,40 +1,43 @@ import 'package:sudoku/utils/tools.dart'; class DefaultGameSettings { + // available game parameters codes static const String parameterCodeLevel = 'level'; static const String parameterCodeSize = 'size'; - static const List<String> availableParameters = [ parameterCodeLevel, parameterCodeSize, ]; + // level: available values static const String levelValueEasy = 'easy'; static const String levelValueMedium = 'medium'; static const String levelValueHard = 'hard'; static const String levelValueNightmare = 'nightmare'; - - static const String defaultLevelValue = levelValueMedium; static const List<String> allowedLevelValues = [ levelValueEasy, levelValueMedium, levelValueHard, levelValueNightmare, ]; + // level: default value + static const String defaultLevelValue = levelValueMedium; + // size: available values static const String sizeValueTiny = '2x2'; static const String sizeValueSmall = '3x2'; static const String sizeValueStandard = '3x3'; static const String sizeValueLarge = '4x4'; - - static const String defaultSizeValue = sizeValueStandard; static const List<String> allowedSizeValues = [ sizeValueTiny, sizeValueSmall, sizeValueStandard, sizeValueLarge, ]; + // size: default value + static const String defaultSizeValue = sizeValueStandard; + // available values from parameter code static List<String> getAvailableValues(String parameterCode) { switch (parameterCode) { case parameterCodeLevel: @@ -46,4 +49,9 @@ class DefaultGameSettings { printlog('Did not find any available value for game parameter "$parameterCode".'); return []; } + + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + // + ]; } diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart index 401e3ef..86bc0e0 100644 --- a/lib/config/default_global_settings.dart +++ b/lib/config/default_global_settings.dart @@ -1,30 +1,33 @@ import 'package:sudoku/utils/tools.dart'; class DefaultGlobalSettings { + // available global parameters codes static const String parameterCodeSkin = 'skin'; - static const List<String> availableParameters = [ parameterCodeSkin, ]; + // skin: available values static const String skinValueDigits = 'digits'; static const String skinValueFood = 'food'; static const String skinValueNature = 'nature'; static const String skinValueMonsters = 'monsters'; - - static const String defaultSkinValue = skinValueDigits; static const List<String> allowedSkinValues = [ skinValueDigits, skinValueFood, skinValueNature, skinValueMonsters, ]; + // skin: default value + static const String defaultSkinValue = skinValueDigits; + // skin: shufflable skins (without order in items) static const List<String> shufflableSkins = [ skinValueFood, skinValueNature, skinValueMonsters, ]; + // available values from parameter code static List<String> getAvailableValues(String parameterCode) { switch (parameterCode) { case parameterCodeSkin: @@ -35,5 +38,10 @@ class DefaultGlobalSettings { return []; } + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + parameterCodeSkin, + ]; + static const int defaultTipCountDownValueInSeconds = 20; } diff --git a/lib/config/menu.dart b/lib/config/menu.dart index c092272..64fb1ac 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -1,17 +1,15 @@ import 'package:flutter/material.dart'; import 'package:unicons/unicons.dart'; -import 'package:sudoku/ui/screens/about_page.dart'; -import 'package:sudoku/ui/screens/game_page.dart'; -import 'package:sudoku/ui/screens/settings_page.dart'; +import 'package:sudoku/ui/screens/page_about.dart'; +import 'package:sudoku/ui/screens/page_game.dart'; +import 'package:sudoku/ui/screens/page_settings.dart'; class MenuItem { - final String code; final Icon icon; final Widget page; const MenuItem({ - required this.code, required this.icon, required this.page, }); @@ -20,23 +18,20 @@ class MenuItem { class Menu { static const indexGame = 0; static const menuItemGame = MenuItem( - code: 'bottom_nav_game', icon: Icon(UniconsLine.home), - page: GamePage(), + page: PageGame(), ); static const indexSettings = 1; static const menuItemSettings = MenuItem( - code: 'bottom_nav_settings', icon: Icon(UniconsLine.setting), - page: SettingsPage(), + page: PageSettings(), ); static const indexAbout = 2; static const menuItemAbout = MenuItem( - code: 'bottom_nav_about', icon: Icon(UniconsLine.info_circle), - page: AboutPage(), + page: PageAbout(), ); static Map<int, MenuItem> items = { diff --git a/lib/config/theme.dart b/lib/config/theme.dart index 1732b79..74f532f 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -39,11 +39,9 @@ final ColorScheme lightColorScheme = ColorScheme.light( secondary: primarySwatch.shade500, onSecondary: Colors.white, error: errorColor, - background: textSwatch.shade200, - onBackground: textSwatch.shade500, onSurface: textSwatch.shade500, surface: textSwatch.shade50, - surfaceVariant: Colors.white, + surfaceContainerHighest: Colors.white, shadow: textSwatch.shade900.withOpacity(.1), ); @@ -52,11 +50,9 @@ final ColorScheme darkColorScheme = ColorScheme.dark( 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), + surfaceContainerHighest: const Color(0xFF282832), shadow: textSwatch.shade900.withOpacity(.2), ); @@ -192,5 +188,3 @@ final ThemeData darkTheme = lightTheme.copyWith( ), ), ); - -final ThemeData appTheme = lightTheme; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart index bb663d0..1fca0d0 100644 --- a/lib/cubit/game_cubit.dart +++ b/lib/cubit/game_cubit.dart @@ -5,12 +5,12 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:sudoku/config/default_global_settings.dart'; -import 'package:sudoku/models/cell.dart'; -import 'package:sudoku/models/cell_location.dart'; -import 'package:sudoku/models/game.dart'; -import 'package:sudoku/models/settings_game.dart'; -import 'package:sudoku/models/settings_global.dart'; +import 'package:sudoku/models/game/cell.dart'; +import 'package:sudoku/models/game/cell_location.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/models/settings/settings_game.dart'; +import 'package:sudoku/models/settings/settings_global.dart'; import 'package:sudoku/utils/board_animate.dart'; part 'game_state.dart'; @@ -18,36 +18,43 @@ part 'game_state.dart'; class GameCubit extends HydratedCubit<GameState> { GameCubit() : super(GameState( - game: Game.createNull(), + currentGame: Game.createEmpty(), )); void updateState(Game game) { emit(GameState( - game: game, + currentGame: game, )); } void refresh() { - updateState(Game( - gameSettings: state.game.gameSettings, - globalSettings: state.game.globalSettings, - board: state.game.board, - solvedBoard: state.game.solvedBoard, - isRunning: state.game.isRunning, - isStarted: state.game.isStarted, - isFinished: state.game.isFinished, - blockSizeHorizontal: state.game.blockSizeHorizontal, - blockSizeVertical: state.game.blockSizeVertical, - boardSize: state.game.boardSize, - shuffledCellValues: state.game.shuffledCellValues, - boardConflicts: state.game.boardConflicts, - selectedCell: state.game.selectedCell, - showConflicts: state.game.showConflicts, - givenTipsCount: state.game.givenTipsCount, - buttonTipsCountdown: state.game.buttonTipsCountdown, - animationInProgress: state.game.animationInProgress, - boardAnimated: state.game.boardAnimated, - )); + final Game game = Game( + // Settings + gameSettings: state.currentGame.gameSettings, + globalSettings: state.currentGame.globalSettings, + // State + isRunning: state.currentGame.isRunning, + isStarted: state.currentGame.isStarted, + isFinished: state.currentGame.isFinished, + animationInProgress: state.currentGame.animationInProgress, + boardAnimated: state.currentGame.boardAnimated, + // Base data + board: state.currentGame.board, + solvedBoard: state.currentGame.solvedBoard, + blockSizeHorizontal: state.currentGame.blockSizeHorizontal, + blockSizeVertical: state.currentGame.blockSizeVertical, + boardSize: state.currentGame.boardSize, + // Game data + shuffledCellValues: state.currentGame.shuffledCellValues, + boardConflicts: state.currentGame.boardConflicts, + selectedCell: state.currentGame.selectedCell, + showConflicts: state.currentGame.showConflicts, + givenTipsCount: state.currentGame.givenTipsCount, + buttonTipsCountdown: state.currentGame.buttonTipsCountdown, + ); + // game.dump(); + + updateState(game); } void startNewGame({ @@ -55,6 +62,7 @@ class GameCubit extends HydratedCubit<GameState> { required GlobalSettings globalSettings, }) { final Game newGame = Game.createNew( + // Settings gameSettings: gameSettings, globalSettings: globalSettings, ); @@ -68,18 +76,18 @@ class GameCubit extends HydratedCubit<GameState> { } void selectCell(CellLocation location) { - state.game.selectedCell = state.game.board.get(location); + state.currentGame.selectedCell = state.currentGame.board.get(location); refresh(); } void unselectCell() { - state.game.selectedCell = null; + state.currentGame.selectedCell = null; refresh(); } void updateCellValue(CellLocation location, int value) { - if (state.game.board.get(location).isFixed == false) { - state.game.board.set( + if (state.currentGame.board.get(location).isFixed == false) { + state.currentGame.board.set( location, Cell( location: location, @@ -87,35 +95,37 @@ class GameCubit extends HydratedCubit<GameState> { isFixed: false, ), ); - state.game.isStarted = true; + state.currentGame.isStarted = true; refresh(); } - if (state.game.checkBoardIsSolved()) { + if (state.currentGame.checkBoardIsSolved()) { BoardAnimate.startAnimation(this, 'win'); - state.game.isFinished = true; + state.currentGame.isFinished = true; refresh(); } } void toggleShowConflicts() { - state.game.showConflicts = !state.game.showConflicts; + state.currentGame.showConflicts = !state.currentGame.showConflicts; refresh(); } void increaseGivenTipsCount() { - state.game.givenTipsCount++; - state.game.buttonTipsCountdown = DefaultGlobalSettings.defaultTipCountDownValueInSeconds; + state.currentGame.givenTipsCount++; + state.currentGame.buttonTipsCountdown = + DefaultGlobalSettings.defaultTipCountDownValueInSeconds; refresh(); const Duration interval = Duration(milliseconds: 500); Timer.periodic( interval, (Timer timer) { - if (state.game.buttonTipsCountdown == 0) { + if (state.currentGame.buttonTipsCountdown == 0) { timer.cancel(); } else { - state.game.buttonTipsCountdown = max(state.game.buttonTipsCountdown - 1, 0); + state.currentGame.buttonTipsCountdown = + max(state.currentGame.buttonTipsCountdown - 1, 0); } refresh(); }, @@ -123,39 +133,39 @@ class GameCubit extends HydratedCubit<GameState> { } void quitGame() { - state.game.isRunning = false; + state.currentGame.isRunning = false; refresh(); } void resumeSavedGame() { - state.game.isRunning = true; + state.currentGame.isRunning = true; refresh(); } void deleteSavedGame() { - state.game.isRunning = false; - state.game.isFinished = true; + state.currentGame.isRunning = false; + state.currentGame.isFinished = true; refresh(); } void updateAnimationInProgress(bool animationInProgress) { - state.game.animationInProgress = animationInProgress; + state.currentGame.animationInProgress = animationInProgress; refresh(); } void setAnimatedBackground(List animatedCellsPattern) { - for (int row = 0; row < state.game.boardSize; row++) { - for (int col = 0; col < state.game.boardSize; col++) { - state.game.boardAnimated[row][col] = animatedCellsPattern[row][col]; + for (int row = 0; row < state.currentGame.boardSize; row++) { + for (int col = 0; col < state.currentGame.boardSize; col++) { + state.currentGame.boardAnimated[row][col] = animatedCellsPattern[row][col]; } } refresh(); } void resetAnimatedBackground() { - for (int row = 0; row < state.game.boardSize; row++) { - for (int col = 0; col < state.game.boardSize; col++) { - state.game.boardAnimated[row][col] = false; + for (int row = 0; row < state.currentGame.boardSize; row++) { + for (int col = 0; col < state.currentGame.boardSize; col++) { + state.currentGame.boardAnimated[row][col] = false; } } refresh(); @@ -163,17 +173,17 @@ class GameCubit extends HydratedCubit<GameState> { @override GameState? fromJson(Map<String, dynamic> json) { - Game game = json['game'] as Game; + final Game currentGame = json['currentGame'] as Game; return GameState( - game: game, + currentGame: currentGame, ); } @override Map<String, dynamic>? toJson(GameState state) { return <String, dynamic>{ - 'game': state.game.toJson(), + 'currentGame': state.currentGame.toJson(), }; } } diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart index 8d8f70f..00e2116 100644 --- a/lib/cubit/game_state.dart +++ b/lib/cubit/game_state.dart @@ -3,17 +3,13 @@ part of 'game_cubit.dart'; @immutable class GameState extends Equatable { const GameState({ - required this.game, + required this.currentGame, }); - final Game game; + final Game currentGame; @override List<dynamic> get props => <dynamic>[ - game, + currentGame, ]; - - Map<String, dynamic> get values => <String, dynamic>{ - 'game': game, - }; } diff --git a/lib/cubit/settings_game_cubit.dart b/lib/cubit/settings_game_cubit.dart index fc2840d..2ac2517 100644 --- a/lib/cubit/settings_game_cubit.dart +++ b/lib/cubit/settings_game_cubit.dart @@ -1,9 +1,9 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:sudoku/config/default_game_settings.dart'; -import 'package:sudoku/models/settings_game.dart'; +import 'package:sudoku/config/default_game_settings.dart'; +import 'package:sudoku/models/settings/settings_game.dart'; part 'settings_game_state.dart'; @@ -31,6 +31,7 @@ class GameSettingsCubit extends HydratedCubit<GameSettingsState> { case DefaultGameSettings.parameterCodeSize: return GameSettings.getSizeValueFromUnsafe(state.settings.size); } + return ''; } diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart index b773dc6..5acd85b 100644 --- a/lib/cubit/settings_game_state.dart +++ b/lib/cubit/settings_game_state.dart @@ -12,8 +12,4 @@ class GameSettingsState extends Equatable { List<dynamic> get props => <dynamic>[ settings, ]; - - Map<String, dynamic> get values => <String, dynamic>{ - 'settings': settings, - }; } diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart index 9dceaa0..f14c602 100644 --- a/lib/cubit/settings_global_cubit.dart +++ b/lib/cubit/settings_global_cubit.dart @@ -1,9 +1,9 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:sudoku/config/default_global_settings.dart'; -import 'package:sudoku/models/settings_global.dart'; +import 'package:sudoku/config/default_global_settings.dart'; +import 'package:sudoku/models/settings/settings_global.dart'; part 'settings_global_state.dart'; diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart index 4e4fbdf..ebcddd7 100644 --- a/lib/cubit/settings_global_state.dart +++ b/lib/cubit/settings_global_state.dart @@ -12,8 +12,4 @@ class GlobalSettingsState extends Equatable { List<dynamic> get props => <dynamic>[ settings, ]; - - Map<String, dynamic> get values => <String, dynamic>{ - 'settings': settings, - }; } diff --git a/lib/assets/grids.dart b/lib/data/game_data.dart similarity index 99% rename from lib/assets/grids.dart rename to lib/data/game_data.dart index c3cea19..3e43fcf 100644 --- a/lib/assets/grids.dart +++ b/lib/data/game_data.dart @@ -1,4 +1,4 @@ -class SudokuGrids { +class GameData { static const Map<String, Map<String, List<String>>> templates = { '2x2': { 'easy': [ diff --git a/lib/main.dart b/lib/main.dart index a69d933..c6929f7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hive/hive.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; @@ -26,18 +27,17 @@ void main() async { storageDirectory: tmpDir, ); - runApp( - EasyLocalization( - path: 'assets/translations', - supportedLocales: const <Locale>[ - Locale('en'), - Locale('fr'), - ], - fallbackLocale: const Locale('en'), - useFallbackTranslations: true, - child: const MyApp(), - ), - ); + 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 { @@ -85,16 +85,18 @@ class MyApp extends StatelessWidget { final List<String> gameImages = [ 'button_back', + 'button_delete_saved_game', 'button_help', + 'button_resume_game', 'button_show_conflicts', 'button_start', + 'cell_empty', 'game_win', 'placeholder', - 'cell_empty' ]; for (String image in gameImages) { - assets.add('${'assets/icons/$image'}.png'); + assets.add('assets/ui/$image.png'); } List<String> skinImages = []; @@ -103,9 +105,9 @@ class MyApp extends StatelessWidget { } for (String skin in DefaultGlobalSettings.allowedSkinValues) { - assets.add('assets/icons/skin_$skin.png'); + assets.add('assets/ui/skin_$skin.png'); for (String image in skinImages) { - assets.add('${'${'assets/skins/$skin'}_$image'}.png'); + assets.add('assets/skins/${skin}_$image.png'); } } diff --git a/lib/models/board.dart b/lib/models/game/board.dart similarity index 54% rename from lib/models/board.dart rename to lib/models/game/board.dart index c83989e..852cc21 100644 --- a/lib/models/board.dart +++ b/lib/models/game/board.dart @@ -1,8 +1,8 @@ import 'dart:math'; -import 'package:sudoku/models/cell.dart'; -import 'package:sudoku/models/cell_location.dart'; -import 'package:sudoku/models/types.dart'; +import 'package:sudoku/models/game/cell.dart'; +import 'package:sudoku/models/game/cell_location.dart'; +import 'package:sudoku/models/game/types.dart'; import 'package:sudoku/utils/tools.dart'; class Board { @@ -26,6 +26,145 @@ class Board { ); } + factory Board.createFromTemplate({ + required String template, + required bool isSymetric, + }) { + // Create board cells from size, with "empty" cells + BoardCells createEmptyBoard(final int boardSize) { + final BoardCells cells = []; + for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { + final List<Cell> row = []; + for (int colIndex = 0; colIndex < boardSize; colIndex++) { + row.add(Cell( + location: CellLocation.go(rowIndex, colIndex), + value: 0, + isFixed: false, + )); + } + cells.add(row); + } + + return cells; + } + + BoardCells cells = []; + final int boardSize = int.parse(pow(template.length, 1 / 2).toStringAsFixed(0)); + + const String stringValues = '0123456789ABCDEFG'; + + int index = 0; + for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { + final List<Cell> row = []; + for (int colIndex = 0; colIndex < boardSize; colIndex++) { + final String stringValue = template[index++]; + final int value = stringValues.indexOf(stringValue); + row.add(Cell( + location: CellLocation.go(rowIndex, colIndex), + value: value, + isFixed: (value != 0), + )); + } + cells.add(row); + } + + const List<String> allowedFlip = ['none', 'horizontal', 'vertical']; + List<String> allowedRotate = ['none', 'left', 'right']; + + // Forbid rotation if blocks are not symetric + if (!isSymetric) { + allowedRotate = ['none']; + } + + final Random rand = Random(); + final String flip = allowedFlip[rand.nextInt(allowedFlip.length)]; + final String rotate = allowedRotate[rand.nextInt(allowedRotate.length)]; + + printlog('flip board: $flip'); + printlog('rotate board: $rotate'); + + switch (flip) { + case 'horizontal': + { + final BoardCells transformedBoard = createEmptyBoard(boardSize); + for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { + for (int colIndex = 0; colIndex < boardSize; colIndex++) { + transformedBoard[rowIndex][colIndex] = Cell( + location: CellLocation.go(rowIndex, colIndex), + value: cells[boardSize - rowIndex - 1][colIndex].value, + isFixed: false, + ); + } + } + cells = transformedBoard; + } + break; + case 'vertical': + { + final BoardCells transformedBoard = createEmptyBoard(boardSize); + for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { + for (int colIndex = 0; colIndex < boardSize; colIndex++) { + transformedBoard[rowIndex][colIndex] = Cell( + location: CellLocation.go(rowIndex, colIndex), + value: cells[rowIndex][boardSize - colIndex - 1].value, + isFixed: false, + ); + } + } + cells = transformedBoard; + } + break; + } + + switch (rotate) { + case 'left': + { + final BoardCells transformedBoard = createEmptyBoard(boardSize); + for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { + for (int colIndex = 0; colIndex < boardSize; colIndex++) { + transformedBoard[rowIndex][colIndex] = Cell( + location: CellLocation.go(rowIndex, colIndex), + value: cells[colIndex][boardSize - rowIndex - 1].value, + isFixed: false, + ); + } + } + cells = transformedBoard; + } + break; + case 'right': + { + final BoardCells transformedBoard = createEmptyBoard(boardSize); + for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { + for (int colIndex = 0; colIndex < boardSize; colIndex++) { + transformedBoard[rowIndex][colIndex] = Cell( + location: CellLocation.go(rowIndex, colIndex), + value: cells[boardSize - colIndex - 1][rowIndex].value, + isFixed: false, + ); + } + } + cells = transformedBoard; + } + break; + } + + // Fix cells fixed states + for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { + for (int colIndex = 0; colIndex < boardSize; colIndex++) { + cells[rowIndex][colIndex] = Cell( + location: CellLocation.go(rowIndex, colIndex), + value: cells[rowIndex][colIndex].value, + isFixed: (cells[rowIndex][colIndex].value != 0) ? true : false, + ); + } + } + + return Board.createNew( + cells: cells, + ); + } + Cell get(CellLocation location) { if (location.row < cells.length) { if (location.col < cells[location.row].length) { diff --git a/lib/models/cell.dart b/lib/models/game/cell.dart similarity index 92% rename from lib/models/cell.dart rename to lib/models/game/cell.dart index c8e0717..8da6e6a 100644 --- a/lib/models/cell.dart +++ b/lib/models/game/cell.dart @@ -1,4 +1,4 @@ -import 'package:sudoku/models/cell_location.dart'; +import 'package:sudoku/models/game/cell_location.dart'; import 'package:sudoku/utils/tools.dart'; class Cell { diff --git a/lib/models/cell_location.dart b/lib/models/game/cell_location.dart similarity index 100% rename from lib/models/cell_location.dart rename to lib/models/game/cell_location.dart diff --git a/lib/models/game.dart b/lib/models/game/game.dart similarity index 82% rename from lib/models/game.dart rename to lib/models/game/game.dart index 74bc3b4..c59d001 100644 --- a/lib/models/game.dart +++ b/lib/models/game/game.dart @@ -1,78 +1,85 @@ import 'dart:math'; -import 'package:sudoku/assets/grids.dart'; import 'package:sudoku/config/default_global_settings.dart'; import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/models/board.dart'; -import 'package:sudoku/models/cell.dart'; -import 'package:sudoku/models/cell_location.dart'; -import 'package:sudoku/models/settings_game.dart'; -import 'package:sudoku/models/settings_global.dart'; -import 'package:sudoku/models/types.dart'; -import 'package:sudoku/utils/board_utils.dart'; +import 'package:sudoku/data/game_data.dart'; +import 'package:sudoku/models/game/board.dart'; +import 'package:sudoku/models/game/cell.dart'; +import 'package:sudoku/models/game/cell_location.dart'; +import 'package:sudoku/models/game/types.dart'; +import 'package:sudoku/models/settings/settings_game.dart'; +import 'package:sudoku/models/settings/settings_global.dart'; import 'package:sudoku/utils/tools.dart'; class Game { Game({ + // Settings required this.gameSettings, required this.globalSettings, + + // State + this.isRunning = false, + this.isStarted = false, + this.isFinished = false, + this.animationInProgress = false, + this.boardAnimated = const [], + + // Base data required this.board, required this.solvedBoard, - required this.isRunning, - required this.isStarted, - required this.isFinished, + required this.blockSizeHorizontal, + required this.blockSizeVertical, + required this.boardSize, + + // Game data this.shuffledCellValues = const [], this.boardConflicts = const [], this.selectedCell, this.showConflicts = false, this.givenTipsCount = 0, this.buttonTipsCountdown = 0, - this.animationInProgress = false, - this.boardAnimated = const [], - required this.blockSizeHorizontal, - required this.blockSizeVertical, - required this.boardSize, }); + // Settings final GameSettings gameSettings; final GlobalSettings globalSettings; - bool isRunning = false; - bool isStarted = false; - bool isFinished = false; - - int blockSizeVertical = 0; - int blockSizeHorizontal = 0; - int boardSize = 0; - - Board board; - Board solvedBoard; - - List<int> shuffledCellValues = []; + // State + bool isRunning; + bool isStarted; + bool isFinished; + bool animationInProgress; + AnimatedBoard boardAnimated; + + // Base data + final Board board; + final Board solvedBoard; + final int blockSizeHorizontal; + final int blockSizeVertical; + final int boardSize; + + // Game data + List<int> shuffledCellValues; + ConflictsCount boardConflicts; Cell? selectedCell; + bool showConflicts; + int givenTipsCount; + int buttonTipsCountdown; - int givenTipsCount = 0; - int buttonTipsCountdown = 0; - bool showConflicts = false; - ConflictsCount boardConflicts = []; - - bool animationInProgress = false; - AnimatedBoard boardAnimated = []; - - factory Game.createNull() { + factory Game.createEmpty() { return Game( + // Settings gameSettings: GameSettings.createDefault(), globalSettings: GlobalSettings.createDefault(), + // Base data board: Board.createEmpty(), solvedBoard: Board.createEmpty(), - shuffledCellValues: [], - isRunning: false, - isStarted: false, - isFinished: false, - givenTipsCount: 0, blockSizeHorizontal: 0, blockSizeVertical: 0, boardSize: 0, + // Game data + shuffledCellValues: [], + givenTipsCount: 0, ); } @@ -106,15 +113,15 @@ class Game { } final List<String> templates = - SudokuGrids.templates[newGameSettings.size]?[newGameSettings.level] ?? []; + GameData.templates[newGameSettings.size]?[newGameSettings.level] ?? []; final String template = templates.elementAt(Random().nextInt(templates.length)).toString(); if (template.length != pow(blockSizeHorizontal * blockSizeVertical, 2)) { printlog('Failed to get grid template...'); - return Game.createNull(); + return Game.createEmpty(); } - final Board board = BoardUtils.createBoardFromTemplate( + final Board board = Board.createFromTemplate( template: template, isSymetric: (blockSizeHorizontal == blockSizeVertical), ); @@ -131,26 +138,30 @@ class Game { } return Game( + // Settings gameSettings: newGameSettings, globalSettings: newGlobalSettings, + // State + isRunning: true, + boardAnimated: notAnimatedBoard, + // Base data board: board, solvedBoard: solvedBoard, - isRunning: true, - isStarted: false, - isFinished: false, - boardConflicts: nonConflictedBoard, - shuffledCellValues: shuffledCellValues, - selectedCell: null, blockSizeHorizontal: blockSizeHorizontal, blockSizeVertical: blockSizeVertical, boardSize: boardSize, - boardAnimated: notAnimatedBoard, + // Game data + shuffledCellValues: shuffledCellValues, + boardConflicts: nonConflictedBoard, + selectedCell: null, ); } - bool canGiveTip() { - return (buttonTipsCountdown == 0); - } + bool get canBeResumed => isStarted && !isFinished; + + bool get gameWon => isRunning && isStarted && isFinished; + + bool get canGiveTip => (buttonTipsCountdown == 0); int getTranslatedValueForDisplay(int originalValue) { return shuffledCellValues[originalValue - 1]; @@ -274,7 +285,6 @@ class Game { // pick one of wrong value cells, if found final List<List<int>> wrongValueCells = getCellsWithWrongValue(); if (wrongValueCells.isNotEmpty) { - printlog('will pick from wrongValueCells'); pickRandomFromList(gameCubit, wrongValueCells); return; } @@ -282,7 +292,6 @@ class Game { // pick one of conflicting cells, if found final List<List<int>> conflictingCells = getCellsWithConflicts(); if (conflictingCells.isNotEmpty) { - printlog('will pick from conflictingCells'); pickRandomFromList(gameCubit, conflictingCells); return; } @@ -290,7 +299,6 @@ class Game { // pick one form cells with unique non-conflicting candidate value final List<List<int>> candidateCells = board.getEmptyCellsWithUniqueAvailableValue(); if (candidateCells.isNotEmpty) { - printlog('will pick from candidateCells'); pickRandomFromList(gameCubit, candidateCells); return; } @@ -354,6 +362,35 @@ class Game { gameCubit.unselectCell(); } + void dump() { + printlog(''); + printlog('## Current game dump:'); + printlog(''); + printlog('$Game:'); + printlog(' Settings'); + gameSettings.dump(); + globalSettings.dump(); + printlog(' State'); + printlog(' isRunning: $isRunning'); + printlog(' isStarted: $isStarted'); + printlog(' isFinished: $isFinished'); + printlog(' animationInProgress: $animationInProgress'); + printlog(' Base data'); + printlog(' blockSizeHorizontal: $blockSizeHorizontal'); + printlog(' blockSizeVertical: $blockSizeVertical'); + printlog(' boardSize: $boardSize'); + printlog(' Game data'); + printlog(' shuffledCellValues: $shuffledCellValues'); + printlog(' selectedCell: ${selectedCell?.toString() ?? ''}'); + printlog(' showConflicts: $showConflicts'); + printlog(' givenTipsCount: $givenTipsCount'); + printlog(' buttonTipsCountdown: $buttonTipsCountdown'); + printlog(''); + + printGrid(); + printlog(''); + } + printGrid() { final BoardCells cells = board.cells; final BoardCells solvedCells = solvedBoard.cells; @@ -374,33 +411,6 @@ class Game { printlog(''); } - void dump() { - printlog(''); - printlog('## Current game dump:'); - printlog(''); - gameSettings.dump(); - globalSettings.dump(); - printlog(''); - printlog('$Game:'); - printlog(' blockSizeHorizontal: $blockSizeHorizontal'); - printlog(' blockSizeVertical: $blockSizeVertical'); - printlog(' boardSize: $boardSize'); - printlog(' shuffledCellValues: $shuffledCellValues'); - printlog(''); - printlog(' isRunning: $isRunning'); - printlog(' isStarted: $isStarted'); - printlog(' isFinished: $isFinished'); - printlog(' selectedCell: ${selectedCell?.toString() ?? ''}'); - printlog(' showConflicts: $showConflicts'); - printlog(' givenTipsCount: $givenTipsCount'); - printlog(' givenTipsCountEnableCountdown: $buttonTipsCountdown'); - printlog(' animationInProgress: $animationInProgress'); - printlog(''); - - printGrid(); - printlog(''); - } - @override String toString() { return '$Game(${toJson()})'; @@ -408,22 +418,28 @@ class Game { Map<String, dynamic>? toJson() { return <String, dynamic>{ + // Settings 'gameSettings': gameSettings.toJson(), 'globalSettings': globalSettings.toJson(), + // State 'isRunning': isRunning, + 'isStarted': isStarted, 'isFinished': isFinished, + 'animationInProgress': animationInProgress, + 'boardAnimated': boardAnimated, + // Base data 'board': board.toJson(), 'solvedBoard': solvedBoard.toJson(), + 'blockSizeHorizontal': blockSizeHorizontal, + 'blockSizeVertical': blockSizeVertical, + 'boardSize': boardSize, + // Game data 'shuffledCellValues': shuffledCellValues, 'boardConflicts': boardConflicts, 'selectedCell': selectedCell?.toJson(), 'showConflicts': showConflicts, 'givenTipsCount': givenTipsCount, - 'givenTipsCountEnableCountdown': buttonTipsCountdown, - 'animationInProgress': animationInProgress, - 'boardAnimated': boardAnimated, - 'blockSizeHorizontal': blockSizeHorizontal, - 'blockSizeVertical': blockSizeVertical, + 'buttonTipsCountdown': buttonTipsCountdown, }; } } diff --git a/lib/models/types.dart b/lib/models/game/types.dart similarity index 79% rename from lib/models/types.dart rename to lib/models/game/types.dart index 9f75508..6fbdd10 100644 --- a/lib/models/types.dart +++ b/lib/models/game/types.dart @@ -1,4 +1,4 @@ -import 'package:sudoku/models/cell.dart'; +import 'package:sudoku/models/game/cell.dart'; typedef BoardCells = List<List<Cell>>; typedef ConflictsCount = List<List<int>>; diff --git a/lib/models/settings_game.dart b/lib/models/settings/settings_game.dart similarity index 96% rename from lib/models/settings_game.dart rename to lib/models/settings/settings_game.dart index 8786fc9..17380bd 100644 --- a/lib/models/settings_game.dart +++ b/lib/models/settings/settings_game.dart @@ -2,8 +2,8 @@ import 'package:sudoku/config/default_game_settings.dart'; import 'package:sudoku/utils/tools.dart'; class GameSettings { - String level; - String size; + final String level; + final String size; GameSettings({ required this.level, diff --git a/lib/models/settings_global.dart b/lib/models/settings/settings_global.dart similarity index 100% rename from lib/models/settings_global.dart rename to lib/models/settings/settings_global.dart diff --git a/lib/ui/game/game_bottom.dart b/lib/ui/game/game_bottom.dart new file mode 100644 index 0000000..954defb --- /dev/null +++ b/lib/ui/game/game_bottom.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/ui/widgets/game/bar_select_cell_value.dart'; + +class GameBottomWidget extends StatelessWidget { + const GameBottomWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return currentGame.isFinished ? const SizedBox.shrink() : const SelectCellValueBar(); + }, + ); + } +} diff --git a/lib/ui/widgets/message_game_end.dart b/lib/ui/game/game_end.dart similarity index 63% rename from lib/ui/widgets/message_game_end.dart rename to lib/ui/game/game_end.dart index 8716d31..d24db59 100644 --- a/lib/ui/widgets/message_game_end.dart +++ b/lib/ui/game/game_end.dart @@ -2,20 +2,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/models/game.dart'; -import 'package:sudoku/ui/widgets/button_game_restart.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/ui/widgets/actions/button_game_quit.dart'; -class EndGameMessage extends StatelessWidget { - const EndGameMessage({super.key}); +class GameEndWidget extends StatelessWidget { + const GameEndWidget({super.key}); @override Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( builder: (BuildContext context, GameState gameState) { - final Game game = gameState.game; + final Game currentGame = gameState.currentGame; - const Image decorationImage = Image( - image: AssetImage('assets/icons/game_win.png'), + final Image decorationImage = Image( + image: AssetImage( + currentGame.gameWon ? 'assets/ui/game_win.png' : 'assets/ui/game_fail.png'), fit: BoxFit.fill, ); @@ -27,17 +28,17 @@ class EndGameMessage extends StatelessWidget { children: [ TableRow( children: [ - const Column( + Column( children: [decorationImage], ), Column( children: [ - game.animationInProgress == true + currentGame.animationInProgress == true ? decorationImage - : const RestartGameButton() + : const QuitGameButton() ], ), - const Column( + Column( children: [decorationImage], ), ], diff --git a/lib/ui/game/game_top.dart b/lib/ui/game/game_top.dart new file mode 100644 index 0000000..5bac69a --- /dev/null +++ b/lib/ui/game/game_top.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/ui/widgets/game/button_show_conflicts.dart'; +import 'package:sudoku/ui/widgets/game/button_show_tip.dart'; + +class GameTopWidget extends StatelessWidget { + const GameTopWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return SizedBox( + height: 60, + child: (currentGame.isRunning && !currentGame.isFinished) + ? const Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ButtonShowTip(), + ButtonShowConflicts(), + ], + ) + : const SizedBox.shrink(), + ); + }, + ); + } +} diff --git a/lib/ui/helpers/app_titles.dart b/lib/ui/helpers/app_titles.dart new file mode 100644 index 0000000..b98107b --- /dev/null +++ b/lib/ui/helpers/app_titles.dart @@ -0,0 +1,32 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class AppHeader extends StatelessWidget { + const AppHeader({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + ); + } +} + +class AppTitle extends StatelessWidget { + const AppTitle({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.titleLarge!.apply(fontWeightDelta: 2), + ); + } +} diff --git a/lib/ui/helpers/outlined_text_widget.dart b/lib/ui/helpers/outlined_text_widget.dart new file mode 100644 index 0000000..342eb31 --- /dev/null +++ b/lib/ui/helpers/outlined_text_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +import 'package:sudoku/utils/color_extensions.dart'; + +class OutlinedText extends StatelessWidget { + const OutlinedText({ + super.key, + required this.text, + required this.fontSize, + required this.textColor, + this.outlineColor, + }); + + final String text; + final double fontSize; + final Color textColor; + final Color? outlineColor; + + @override + Widget build(BuildContext context) { + final double delta = fontSize / 30; + + return Text( + text, + style: TextStyle( + inherit: true, + fontSize: fontSize, + fontWeight: FontWeight.w600, + color: textColor, + shadows: [ + Shadow( + offset: Offset(-delta, -delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(delta, -delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(delta, delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(-delta, delta), + color: outlineColor ?? textColor.darken(), + ), + ], + ), + ); + } +} diff --git a/lib/ui/widgets/game.dart b/lib/ui/layouts/game_layout.dart similarity index 53% rename from lib/ui/widgets/game.dart rename to lib/ui/layouts/game_layout.dart index b9160d1..349f9c2 100644 --- a/lib/ui/widgets/game.dart +++ b/lib/ui/layouts/game_layout.dart @@ -2,32 +2,35 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/models/game.dart'; -import 'package:sudoku/ui/layout/board.dart'; -import 'package:sudoku/ui/widgets/message_game_end.dart'; -import 'package:sudoku/ui/widgets/bar_select_cell_value.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/ui/game/game_bottom.dart'; +import 'package:sudoku/ui/game/game_end.dart'; +import 'package:sudoku/ui/game/game_top.dart'; +import 'package:sudoku/ui/widgets/game/game_board.dart'; -class GameWidget extends StatelessWidget { - const GameWidget({super.key}); +class GameLayout extends StatelessWidget { + const GameLayout({super.key}); @override Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( builder: (BuildContext context, GameState gameState) { - final Game game = gameState.game; + final Game currentGame = gameState.currentGame; 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 BoardLayout(), + const GameBoardWidget(), const SizedBox(height: 8), - game.isFinished ? const SizedBox.shrink() : const SelectCellValueBar(), + const GameBottomWidget(), const Expanded(child: SizedBox.shrink()), - game.isFinished ? const EndGameMessage() : const SizedBox.shrink(), + currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(), ], ), ); diff --git a/lib/ui/widgets/parameters.dart b/lib/ui/layouts/parameters_layout.dart similarity index 67% rename from lib/ui/widgets/parameters.dart rename to lib/ui/layouts/parameters_layout.dart index 4018b05..22411b7 100644 --- a/lib/ui/widgets/parameters.dart +++ b/lib/ui/layouts/parameters_layout.dart @@ -5,14 +5,14 @@ import 'package:sudoku/config/default_game_settings.dart'; import 'package:sudoku/config/default_global_settings.dart'; import 'package:sudoku/cubit/settings_game_cubit.dart'; import 'package:sudoku/cubit/settings_global_cubit.dart'; -import 'package:sudoku/ui/painters/parameter_painter.dart'; -import 'package:sudoku/ui/widgets/button_delete_saved_game.dart'; -import 'package:sudoku/ui/widgets/button_game_start_new.dart'; -import 'package:sudoku/ui/widgets/button_resume_saved_game.dart'; -import 'package:sudoku/ui/widgets/parameter_image.dart'; +import 'package:sudoku/ui/parameters/parameter_image.dart'; +import 'package:sudoku/ui/parameters/parameter_painter.dart'; +import 'package:sudoku/ui/widgets/actions/button_delete_saved_game.dart'; +import 'package:sudoku/ui/widgets/actions/button_game_start_new.dart'; +import 'package:sudoku/ui/widgets/actions/button_resume_saved_game.dart'; -class Parameters extends StatelessWidget { - const Parameters({super.key, required this.canResume}); +class ParametersLayout extends StatelessWidget { + const ParametersLayout({super.key, required this.canResume}); final bool canResume; @@ -22,8 +22,6 @@ class Parameters extends StatelessWidget { Widget build(BuildContext context) { final List<Widget> lines = []; - lines.add(SizedBox(height: separatorHeight)); - // Game settings for (String code in DefaultGameSettings.availableParameters) { lines.add(Row( @@ -38,26 +36,35 @@ class Parameters extends StatelessWidget { } lines.add(SizedBox(height: separatorHeight)); - lines.add(Expanded( - child: canResume ? const ResumeSavedGameButton() : const StartNewGameButton(), - )); - lines.add(SizedBox.square( - dimension: MediaQuery.of(context).size.width / 4, - child: canResume ? const DeleteSavedGameButton() : const SizedBox.shrink(), - )); + + if (canResume == false) { + // Start new game + lines.add(const Expanded( + child: StartNewGameButton(), + )); + } else { + // Resume game + lines.add(const Expanded( + child: ResumeSavedGameButton(), + )); + // Delete saved game + lines.add(SizedBox.square( + dimension: MediaQuery.of(context).size.width / 4, + child: const DeleteSavedGameButton(), + )); + } + lines.add(SizedBox(height: separatorHeight)); // Global settings for (String code in DefaultGlobalSettings.availableParameters) { - lines.add( - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: buildParametersLine( - code: code, - isGlobal: true, - ), + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: true, ), - ); + )); lines.add(SizedBox(height: separatorHeight)); } @@ -77,6 +84,10 @@ class Parameters extends StatelessWidget { ? DefaultGlobalSettings.getAvailableValues(code) : DefaultGameSettings.getAvailableValues(code); + if (availableValues.length <= 1) { + return []; + } + for (String value in availableValues) { final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>( builder: (BuildContext context, GameSettingsState gameSettingsState) { @@ -94,14 +105,24 @@ class Parameters extends StatelessWidget { final bool isActive = (value == currentValue); final double displayWidth = MediaQuery.of(context).size.width; - final double itemWidth = displayWidth / availableValues.length - 25; + final double itemWidth = displayWidth / availableValues.length - 26; - final bool isPainterOrAsset = (code != DefaultGlobalSettings.parameterCodeSkin); + final bool displayedWithAssets = + DefaultGlobalSettings.displayedWithAssets.contains(code) || + DefaultGameSettings.displayedWithAssets.contains(code); return TextButton( child: Container( - child: isPainterOrAsset - ? CustomPaint( + child: displayedWithAssets + ? SizedBox.square( + dimension: itemWidth, + child: ParameterImage( + code: code, + value: value, + isSelected: isActive, + ), + ) + : CustomPaint( size: Size(itemWidth, itemWidth), willChange: false, painter: ParameterPainter( @@ -112,19 +133,13 @@ class Parameters extends StatelessWidget { globalSettings: globalSettingsState.settings, ), isComplex: true, - ) - : SizedBox.square( - dimension: itemWidth, - child: ParameterImage( - code: code, - value: value, - isSelected: isActive, - ), ), ), - onPressed: () => isGlobal - ? globalSettingsCubit.setParameterValue(code, value) - : gameSettingsCubit.setParameterValue(code, value), + onPressed: () { + isGlobal + ? globalSettingsCubit.setParameterValue(code, value) + : gameSettingsCubit.setParameterValue(code, value); + }, ); }, ); diff --git a/lib/ui/widgets/parameter_image.dart b/lib/ui/parameters/parameter_image.dart similarity index 94% rename from lib/ui/widgets/parameter_image.dart rename to lib/ui/parameters/parameter_image.dart index 54787e2..fc4b576 100644 --- a/lib/ui/widgets/parameter_image.dart +++ b/lib/ui/parameters/parameter_image.dart @@ -30,7 +30,7 @@ class ParameterImage extends StatelessWidget { ), ), child: Image( - image: AssetImage('assets/icons/${code}_$value.png'), + image: AssetImage('assets/ui/${code}_$value.png'), fit: BoxFit.fill, ), ); diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/parameters/parameter_painter.dart similarity index 95% rename from lib/ui/painters/parameter_painter.dart rename to lib/ui/parameters/parameter_painter.dart index a409d11..714acdc 100644 --- a/lib/ui/painters/parameter_painter.dart +++ b/lib/ui/parameters/parameter_painter.dart @@ -3,8 +3,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:sudoku/config/default_game_settings.dart'; -import 'package:sudoku/models/settings_game.dart'; -import 'package:sudoku/models/settings_global.dart'; +import 'package:sudoku/models/settings/settings_game.dart'; +import 'package:sudoku/models/settings/settings_global.dart'; import 'package:sudoku/utils/tools.dart'; class ParameterPainter extends CustomPainter { @@ -35,7 +35,7 @@ class ParameterPainter extends CustomPainter { paint.style = PaintingStyle.stroke; paint.color = isSelected ? borderColorEnabled : borderColorDisabled; paint.strokeJoin = StrokeJoin.round; - paint.strokeWidth = 20 / 100 * canvasSize; + paint.strokeWidth = 10; canvas.drawRect( Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint); @@ -66,7 +66,7 @@ class ParameterPainter extends CustomPainter { ) { final paint = Paint(); paint.strokeJoin = StrokeJoin.round; - paint.strokeWidth = 3 / 100 * size; + paint.strokeWidth = 3; paint.color = Colors.grey; paint.style = PaintingStyle.fill; @@ -83,6 +83,7 @@ class ParameterPainter extends CustomPainter { final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, + textAlign: TextAlign.center, ); textPainter.layout(); textPainter.paint( @@ -151,6 +152,7 @@ class ParameterPainter extends CustomPainter { final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, + textAlign: TextAlign.center, ); textPainter.layout(); @@ -201,7 +203,7 @@ class ParameterPainter extends CustomPainter { final paint = Paint(); paint.strokeJoin = StrokeJoin.round; - paint.strokeWidth = 5 / 100 * size; + paint.strokeWidth = 3; // Colored background paint.color = backgroundColor; diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart deleted file mode 100644 index 9a8173f..0000000 --- a/lib/ui/screens/game_page.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/ui/widgets/game.dart'; -import 'package:sudoku/ui/widgets/parameters.dart'; - -class GamePage extends StatelessWidget { - const GamePage({super.key}); - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).colorScheme.background, - child: BlocBuilder<GameCubit, GameState>( - builder: (BuildContext context, GameState gameState) { - return gameState.game.isRunning - ? const GameWidget() - : Parameters( - canResume: gameState.game.isStarted && !gameState.game.isFinished, - ); - }, - ), - ); - } -} diff --git a/lib/ui/screens/about_page.dart b/lib/ui/screens/page_about.dart similarity index 86% rename from lib/ui/screens/about_page.dart rename to lib/ui/screens/page_about.dart index ca78fa0..f007546 100644 --- a/lib/ui/screens/about_page.dart +++ b/lib/ui/screens/page_about.dart @@ -2,10 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:sudoku/ui/widgets/header_app.dart'; +import 'package:sudoku/ui/helpers/app_titles.dart'; -class AboutPage extends StatelessWidget { - const AboutPage({super.key}); +class PageAbout extends StatelessWidget { + const PageAbout({super.key}); @override Widget build(BuildContext context) { @@ -17,7 +17,7 @@ class AboutPage extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: <Widget>[ const SizedBox(height: 8), - const AppHeader(text: 'about_title'), + const AppTitle(text: 'about_title'), const Text('about_content').tr(), FutureBuilder<PackageInfo>( future: PackageInfo.fromPlatform(), diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart new file mode 100644 index 0000000..e3a72cd --- /dev/null +++ b/lib/ui/screens/page_game.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/ui/layouts/game_layout.dart'; +import 'package:sudoku/ui/layouts/parameters_layout.dart'; + +class PageGame extends StatelessWidget { + const PageGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return currentGame.isRunning + ? const GameLayout() + : ParametersLayout(canResume: currentGame.canBeResumed); + }, + ); + } +} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/page_settings.dart similarity index 66% rename from lib/ui/screens/settings_page.dart rename to lib/ui/screens/page_settings.dart index aea7bd5..308e420 100644 --- a/lib/ui/screens/settings_page.dart +++ b/lib/ui/screens/page_settings.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:sudoku/ui/widgets/header_app.dart'; -import 'package:sudoku/ui/widgets/settings/settings_form.dart'; +import 'package:sudoku/ui/helpers/app_titles.dart'; +import 'package:sudoku/ui/settings/settings_form.dart'; -class SettingsPage extends StatelessWidget { - const SettingsPage({super.key}); +class PageSettings extends StatelessWidget { + const PageSettings({super.key}); @override Widget build(BuildContext context) { @@ -16,7 +16,7 @@ class SettingsPage extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: <Widget>[ SizedBox(height: 8), - AppHeader(text: 'settings_title'), + AppTitle(text: 'settings_title'), SizedBox(height: 8), SettingsForm(), ], diff --git a/lib/ui/widgets/settings/settings_form.dart b/lib/ui/settings/settings_form.dart similarity index 96% rename from lib/ui/widgets/settings/settings_form.dart rename to lib/ui/settings/settings_form.dart index 53576c1..bbde015 100644 --- a/lib/ui/widgets/settings/settings_form.dart +++ b/lib/ui/settings/settings_form.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:unicons/unicons.dart'; -import 'package:sudoku/ui/widgets/settings/theme_card.dart'; +import 'package:sudoku/ui/settings/theme_card.dart'; class SettingsForm extends StatefulWidget { const SettingsForm({super.key}); diff --git a/lib/ui/widgets/settings/theme_card.dart b/lib/ui/settings/theme_card.dart similarity index 100% rename from lib/ui/widgets/settings/theme_card.dart rename to lib/ui/settings/theme_card.dart diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index 0923b76..dc4027b 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -13,12 +13,22 @@ class SkeletonScreen extends StatelessWidget { return Scaffold( appBar: const GlobalAppBar(), extendBodyBehindAppBar: false, - body: BlocBuilder<NavCubit, int>( - builder: (BuildContext context, int pageIndex) { - return Menu.getPageWidget(pageIndex); - }, + body: Material( + color: Theme.of(context).colorScheme.surface, + child: BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + return Padding( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), + child: Menu.getPageWidget(pageIndex), + ); + }, + ), ), - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: Theme.of(context).colorScheme.surface, ); } } diff --git a/lib/ui/widgets/actions/button_delete_saved_game.dart b/lib/ui/widgets/actions/button_delete_saved_game.dart new file mode 100644 index 0000000..403b2fb --- /dev/null +++ b/lib/ui/widgets/actions/button_delete_saved_game.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; + +class DeleteSavedGameButton extends StatelessWidget { + const DeleteSavedGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_delete_saved_game.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).deleteSavedGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/actions/button_game_quit.dart b/lib/ui/widgets/actions/button_game_quit.dart new file mode 100644 index 0000000..c25e760 --- /dev/null +++ b/lib/ui/widgets/actions/button_game_quit.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; + +class QuitGameButton extends StatelessWidget { + const QuitGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).quitGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/button_game_start_new.dart b/lib/ui/widgets/actions/button_game_start_new.dart similarity index 72% rename from lib/ui/widgets/button_game_start_new.dart rename to lib/ui/widgets/actions/button_game_start_new.dart index 6cd2d63..619f767 100644 --- a/lib/ui/widgets/button_game_start_new.dart +++ b/lib/ui/widgets/actions/button_game_start_new.dart @@ -14,17 +14,17 @@ class StartNewGameButton extends StatelessWidget { builder: (BuildContext context, GameSettingsState gameSettingsState) { return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( builder: (BuildContext context, GlobalSettingsState globalSettingsState) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - return TextButton( child: const Image( - image: AssetImage('assets/icons/button_start.png'), + image: AssetImage('assets/ui/button_start.png'), fit: BoxFit.fill, ), - onPressed: () => gameCubit.startNewGame( - gameSettings: gameSettingsState.settings, - globalSettings: globalSettingsState.settings, - ), + onPressed: () { + BlocProvider.of<GameCubit>(context).startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ); + }, ); }, ); diff --git a/lib/ui/widgets/actions/button_resume_saved_game.dart b/lib/ui/widgets/actions/button_resume_saved_game.dart new file mode 100644 index 0000000..216a71e --- /dev/null +++ b/lib/ui/widgets/actions/button_resume_saved_game.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; + +class ResumeSavedGameButton extends StatelessWidget { + const ResumeSavedGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_resume_game.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).resumeSavedGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/button_delete_saved_game.dart b/lib/ui/widgets/button_delete_saved_game.dart deleted file mode 100644 index fa18bce..0000000 --- a/lib/ui/widgets/button_delete_saved_game.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:sudoku/cubit/game_cubit.dart'; - -class DeleteSavedGameButton extends StatelessWidget { - const DeleteSavedGameButton({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder<GameCubit, GameState>( - builder: (BuildContext context, GameState gameState) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - - return TextButton( - child: const Image( - image: AssetImage('assets/icons/button_delete_saved_game.png'), - fit: BoxFit.fill, - ), - onPressed: () { - gameCubit.deleteSavedGame(); - }, - ); - }, - ); - } -} diff --git a/lib/ui/widgets/button_game_restart.dart b/lib/ui/widgets/button_game_restart.dart deleted file mode 100644 index 1da7f39..0000000 --- a/lib/ui/widgets/button_game_restart.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:sudoku/cubit/game_cubit.dart'; - -class RestartGameButton extends StatelessWidget { - const RestartGameButton({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder<GameCubit, GameState>( - builder: (BuildContext context, GameState gameState) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - - return TextButton( - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () { - gameCubit.quitGame(); - }, - ); - }, - ); - } -} diff --git a/lib/ui/widgets/button_resume_saved_game.dart b/lib/ui/widgets/button_resume_saved_game.dart deleted file mode 100644 index 316a318..0000000 --- a/lib/ui/widgets/button_resume_saved_game.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:sudoku/cubit/game_cubit.dart'; - -class ResumeSavedGameButton extends StatelessWidget { - const ResumeSavedGameButton({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder<GameCubit, GameState>( - builder: (BuildContext context, GameState gameState) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - - return TextButton( - child: const Image( - image: AssetImage('assets/icons/button_resume_game.png'), - fit: BoxFit.fill, - ), - onPressed: () { - gameCubit.resumeSavedGame(); - }, - ); - }, - ); - } -} diff --git a/lib/ui/widgets/bar_select_cell_value.dart b/lib/ui/widgets/game/bar_select_cell_value.dart similarity index 88% rename from lib/ui/widgets/bar_select_cell_value.dart rename to lib/ui/widgets/game/bar_select_cell_value.dart index 7ace4cb..8751351 100644 --- a/lib/ui/widgets/bar_select_cell_value.dart +++ b/lib/ui/widgets/game/bar_select_cell_value.dart @@ -4,10 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/models/cell.dart'; -import 'package:sudoku/models/cell_location.dart'; -import 'package:sudoku/models/game.dart'; -import 'package:sudoku/ui/widgets/cell_widget_update.dart'; +import 'package:sudoku/models/game/cell.dart'; +import 'package:sudoku/models/game/cell_location.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/ui/widgets/game/cell_update.dart'; class SelectCellValueBar extends StatelessWidget { const SelectCellValueBar({super.key}); @@ -16,7 +16,7 @@ class SelectCellValueBar extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( builder: (BuildContext context, GameState gameState) { - final Game game = gameState.game; + final Game game = gameState.currentGame; final bool isUpdatableCellSelected = (game.selectedCell != null) ? !game.selectedCell!.isFixed : false; diff --git a/lib/ui/widgets/game/button_show_conflicts.dart b/lib/ui/widgets/game/button_show_conflicts.dart new file mode 100644 index 0000000..5326b64 --- /dev/null +++ b/lib/ui/widgets/game/button_show_conflicts.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game/game.dart'; + +class ButtonShowConflicts extends StatelessWidget { + const ButtonShowConflicts({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return TextButton( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: currentGame.showConflicts == true ? Colors.blue : Colors.white, + width: 3, + ), + ), + child: const Image( + image: AssetImage('assets/ui/button_show_conflicts.png'), + fit: BoxFit.fill, + ), + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).toggleShowConflicts(); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/button_show_tip.dart b/lib/ui/widgets/game/button_show_tip.dart new file mode 100644 index 0000000..842d632 --- /dev/null +++ b/lib/ui/widgets/game/button_show_tip.dart @@ -0,0 +1,60 @@ +import 'package:badges/badges.dart' as badges; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/config/default_global_settings.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game/game.dart'; + +class ButtonShowTip extends StatelessWidget { + const ButtonShowTip({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return TextButton( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: Colors.white, + width: 3, + ), + ), + child: badges.Badge( + showBadge: currentGame.givenTipsCount == 0 ? false : true, + badgeStyle: badges.BadgeStyle( + badgeColor: currentGame.givenTipsCount < 10 + ? Colors.green + : currentGame.givenTipsCount < 20 + ? Colors.orange + : Colors.red, + ), + badgeContent: Text( + currentGame.givenTipsCount == 0 ? '' : currentGame.givenTipsCount.toString(), + style: const TextStyle(color: Colors.white), + ), + child: Container( + padding: EdgeInsets.all(15 * + currentGame.buttonTipsCountdown / + DefaultGlobalSettings.defaultTipCountDownValueInSeconds), + child: const Image( + image: AssetImage('assets/ui/button_help.png'), + fit: BoxFit.fill, + ), + ), + ), + ), + onPressed: () { + currentGame.canGiveTip + ? currentGame.showTip(BlocProvider.of<GameCubit>(context)) + : null; + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/cell_widget.dart b/lib/ui/widgets/game/cell.dart similarity index 96% rename from lib/ui/widgets/cell_widget.dart rename to lib/ui/widgets/game/cell.dart index 6bc0200..2a7c4f9 100644 --- a/lib/ui/widgets/cell_widget.dart +++ b/lib/ui/widgets/game/cell.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/models/cell.dart'; -import 'package:sudoku/models/game.dart'; +import 'package:sudoku/models/game/cell.dart'; +import 'package:sudoku/models/game/game.dart'; class CellWidget extends StatelessWidget { const CellWidget({super.key, required this.cell}); @@ -14,7 +14,7 @@ class CellWidget extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( builder: (BuildContext context, GameState gameState) { - final Game game = gameState.game; + final Game game = gameState.currentGame; final String imageAsset = getImageAssetName(game); @@ -60,7 +60,7 @@ class CellWidget extends StatelessWidget { return 'assets/skins/${game.globalSettings.skin}_$cellValue.png'; } - return 'assets/icons/cell_empty.png'; + return 'assets/ui/cell_empty.png'; } // Compute cell background color, from cell state diff --git a/lib/ui/widgets/cell_widget_update.dart b/lib/ui/widgets/game/cell_update.dart similarity index 91% rename from lib/ui/widgets/cell_widget_update.dart rename to lib/ui/widgets/game/cell_update.dart index 37371a5..629829e 100644 --- a/lib/ui/widgets/cell_widget_update.dart +++ b/lib/ui/widgets/game/cell_update.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/models/cell.dart'; -import 'package:sudoku/models/game.dart'; +import 'package:sudoku/models/game/cell.dart'; +import 'package:sudoku/models/game/game.dart'; class CellWidgetUpdate extends StatelessWidget { const CellWidgetUpdate({super.key, this.cell}); @@ -14,7 +14,7 @@ class CellWidgetUpdate extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( builder: (BuildContext context, GameState gameState) { - final Game game = gameState.game; + final Game game = gameState.currentGame; if ((cell?.value ?? 0) < 0) { return Container(); @@ -66,6 +66,6 @@ class CellWidgetUpdate extends StatelessWidget { return 'assets/skins/${game.globalSettings.skin}_$cellValue.png'; } - return 'assets/icons/cell_empty.png'; + return 'assets/ui/cell_empty.png'; } } diff --git a/lib/ui/layout/board.dart b/lib/ui/widgets/game/game_board.dart similarity index 68% rename from lib/ui/layout/board.dart rename to lib/ui/widgets/game/game_board.dart index 188d51c..559a6c0 100644 --- a/lib/ui/layout/board.dart +++ b/lib/ui/widgets/game/game_board.dart @@ -2,20 +2,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/models/cell_location.dart'; -import 'package:sudoku/models/game.dart'; -import 'package:sudoku/ui/widgets/cell_widget.dart'; +import 'package:sudoku/models/game/cell_location.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/ui/widgets/game/cell.dart'; -class BoardLayout extends StatelessWidget { - const BoardLayout({super.key}); +class GameBoardWidget extends StatelessWidget { + const GameBoardWidget({super.key}); @override Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( builder: (BuildContext context, GameState gameState) { - final Game game = gameState.game; + final Game currentGame = gameState.currentGame; - final Color borderColor = Theme.of(context).colorScheme.onBackground; + final Color borderColor = Theme.of(context).colorScheme.onSurface; return Container( margin: const EdgeInsets.all(2), @@ -33,13 +33,14 @@ class BoardLayout extends StatelessWidget { Table( defaultColumnWidth: const IntrinsicColumnWidth(), children: [ - for (int row = 0; row < game.boardSize; row++) + for (int row = 0; row < currentGame.boardSize; row++) TableRow( children: [ - for (int col = 0; col < game.boardSize; col++) + for (int col = 0; col < currentGame.boardSize; col++) Column( children: [ - CellWidget(cell: game.board.get(CellLocation.go(row, col))) + CellWidget( + cell: currentGame.board.get(CellLocation.go(row, col))) ], ), ], diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart index 7a4df12..8bb1ea1 100644 --- a/lib/ui/widgets/global_app_bar.dart +++ b/lib/ui/widgets/global_app_bar.dart @@ -1,12 +1,10 @@ -import 'package:badges/badges.dart' as badges; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:sudoku/config/default_global_settings.dart'; import 'package:sudoku/config/menu.dart'; import 'package:sudoku/cubit/game_cubit.dart'; import 'package:sudoku/cubit/nav_cubit.dart'; -import 'package:sudoku/models/game.dart'; +import 'package:sudoku/models/game/game.dart'; class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { const GlobalAppBar({super.key}); @@ -17,17 +15,15 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { builder: (BuildContext context, GameState gameState) { return BlocBuilder<NavCubit, int>( builder: (BuildContext context, int pageIndex) { - final Game game = gameState.game; + final Game currentGame = gameState.currentGame; final List<Widget> menuActions = []; - if (game.isRunning && !game.isFinished) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - + if (currentGame.isRunning && !currentGame.isFinished) { menuActions.add(TextButton( - onPressed: null, + onPressed: () {}, onLongPress: () { - gameCubit.quitGame(); + BlocProvider.of<GameCubit>(context).quitGame(); }, child: Container( decoration: BoxDecoration( @@ -38,69 +34,12 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { ), ), child: const Image( - image: AssetImage('assets/icons/button_back.png'), + image: AssetImage('assets/ui/button_back.png'), fit: BoxFit.fill, ), ), )); menuActions.add(const Spacer(flex: 6)); - menuActions.add(TextButton( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: Colors.white, - width: 3, - ), - ), - child: badges.Badge( - showBadge: game.givenTipsCount == 0 ? false : true, - badgeStyle: badges.BadgeStyle( - badgeColor: game.givenTipsCount < 10 - ? Colors.green - : game.givenTipsCount < 20 - ? Colors.orange - : Colors.red, - ), - badgeContent: Text( - game.givenTipsCount == 0 ? '' : game.givenTipsCount.toString(), - style: const TextStyle(color: Colors.white), - ), - child: Container( - padding: EdgeInsets.all(15 * - game.buttonTipsCountdown / - DefaultGlobalSettings.defaultTipCountDownValueInSeconds), - child: const Image( - image: AssetImage('assets/icons/button_help.png'), - fit: BoxFit.fill, - ), - ), - ), - ), - onPressed: () { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - game.canGiveTip() ? game.showTip(gameCubit) : null; - }, - )); - menuActions.add(const Spacer()); - menuActions.add(TextButton( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: game.showConflicts == true ? Colors.blue : Colors.white, - width: 3, - ), - ), - child: const Image( - image: AssetImage('assets/icons/button_show_conflicts.png'), - fit: BoxFit.fill, - ), - ), - onPressed: () { - gameCubit.toggleShowConflicts(); - }, - )); } else { if (pageIndex == Menu.indexGame) { // go to Settings page diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/header_app.dart deleted file mode 100644 index b5c5be0..0000000 --- a/lib/ui/widgets/header_app.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; - -class AppHeader extends StatelessWidget { - const AppHeader({super.key, required this.text}); - - final String text; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - tr(text), - textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineSmall!.apply(fontWeightDelta: 2), - ), - const SizedBox(height: 8), - ], - ); - } -} diff --git a/lib/utils/board_animate.dart b/lib/utils/board_animate.dart index 40adc18..a1a5239 100644 --- a/lib/utils/board_animate.dart +++ b/lib/utils/board_animate.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:sudoku/cubit/game_cubit.dart'; -import 'package:sudoku/models/game.dart'; -import 'package:sudoku/models/types.dart'; +import 'package:sudoku/models/game/game.dart'; +import 'package:sudoku/models/game/types.dart'; class BoardAnimate { // Start game animation: blinking tiles @@ -70,7 +70,7 @@ class BoardAnimate { } static void startAnimation(GameCubit gameCubit, String animationType) { - final Game game = gameCubit.state.game; + final Game game = gameCubit.state.currentGame; AnimatedBoardSequence patterns = []; diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart deleted file mode 100644 index 491c3d1..0000000 --- a/lib/utils/board_utils.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'dart:math'; - -import 'package:sudoku/models/board.dart'; -import 'package:sudoku/models/cell.dart'; -import 'package:sudoku/models/cell_location.dart'; -import 'package:sudoku/models/types.dart'; -import 'package:sudoku/utils/tools.dart'; - -class BoardUtils { - static BoardCells createEmptyBoard(final int boardSize) { - final BoardCells cells = []; - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - final List<Cell> row = []; - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - row.add(Cell( - location: CellLocation.go(rowIndex, colIndex), - value: 0, - isFixed: false, - )); - } - cells.add(row); - } - - return cells; - } - - static Board createBoardFromTemplate({ - required String template, - required bool isSymetric, - }) { - BoardCells cells = []; - final int boardSize = int.parse(pow(template.length, 1 / 2).toStringAsFixed(0)); - - const String stringValues = '0123456789ABCDEFG'; - - int index = 0; - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - final List<Cell> row = []; - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - final String stringValue = template[index++]; - final int value = stringValues.indexOf(stringValue); - row.add(Cell( - location: CellLocation.go(rowIndex, colIndex), - value: value, - isFixed: (value != 0), - )); - } - cells.add(row); - } - - const List<String> allowedFlip = ['none', 'horizontal', 'vertical']; - List<String> allowedRotate = ['none', 'left', 'right']; - - // Forbid rotation if blocks are not symetric - if (!isSymetric) { - allowedRotate = ['none']; - } - - final Random rand = Random(); - final String flip = allowedFlip[rand.nextInt(allowedFlip.length)]; - final String rotate = allowedRotate[rand.nextInt(allowedRotate.length)]; - - printlog('flip board: $flip'); - printlog('rotate board: $rotate'); - - switch (flip) { - case 'horizontal': - { - final BoardCells transformedBoard = createEmptyBoard(boardSize); - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - transformedBoard[rowIndex][colIndex] = Cell( - location: CellLocation.go(rowIndex, colIndex), - value: cells[boardSize - rowIndex - 1][colIndex].value, - isFixed: false, - ); - } - } - cells = transformedBoard; - } - break; - case 'vertical': - { - final BoardCells transformedBoard = createEmptyBoard(boardSize); - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - transformedBoard[rowIndex][colIndex] = Cell( - location: CellLocation.go(rowIndex, colIndex), - value: cells[rowIndex][boardSize - colIndex - 1].value, - isFixed: false, - ); - } - } - cells = transformedBoard; - } - break; - } - - switch (rotate) { - case 'left': - { - final BoardCells transformedBoard = createEmptyBoard(boardSize); - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - transformedBoard[rowIndex][colIndex] = Cell( - location: CellLocation.go(rowIndex, colIndex), - value: cells[colIndex][boardSize - rowIndex - 1].value, - isFixed: false, - ); - } - } - cells = transformedBoard; - } - break; - case 'right': - { - final BoardCells transformedBoard = createEmptyBoard(boardSize); - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - transformedBoard[rowIndex][colIndex] = Cell( - location: CellLocation.go(rowIndex, colIndex), - value: cells[boardSize - colIndex - 1][rowIndex].value, - isFixed: false, - ); - } - } - cells = transformedBoard; - } - break; - } - - // Fix cells fixed states - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - cells[rowIndex][colIndex] = Cell( - location: CellLocation.go(rowIndex, colIndex), - value: cells[rowIndex][colIndex].value, - isFixed: (cells[rowIndex][colIndex].value != 0) ? true : false, - ); - } - } - - return Board.createNew( - cells: cells, - ); - } -} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000..4e55e33 --- /dev/null +++ b/lib/utils/color_extensions.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +extension ColorExtension on Color { + Color darken([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = 1 - percent / 100; + return Color.fromARGB( + alpha, + (red * value).round(), + (green * value).round(), + (blue * value).round(), + ); + } + + Color lighten([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = percent / 100; + return Color.fromARGB( + alpha, + (red + ((255 - red) * value)).round(), + (green + ((255 - green) * value)).round(), + (blue + ((255 - blue) * value)).round(), + ); + } + + Color avg(Color other) { + final red = (this.red + other.red) ~/ 2; + final green = (this.green + other.green) ~/ 2; + final blue = (this.blue + other.blue) ~/ 2; + final alpha = (this.alpha + other.alpha) ~/ 2; + return Color.fromARGB(alpha, red, green, blue); + } +} diff --git a/pubspec.lock b/pubspec.lock index 5b7acc5..d715955 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: "432698c31a488dd64c56d4759f20d04844baba5e9e4f2cb1abb9676257918b17" + sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" easy_logger: dependency: transitive description: @@ -114,18 +114,18 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a url: "https://pub.dev" source: hosted - version: "8.1.5" + version: "8.1.6" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "4.0.0" flutter_localizations: dependency: transitive description: flutter @@ -172,18 +172,18 @@ packages: dependency: transitive description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" lints: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" material_color_utilities: dependency: transitive description: @@ -196,10 +196,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" nested: dependency: transitive description: @@ -244,18 +244,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.5" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -284,10 +284,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -316,18 +316,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -433,10 +433,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.1" xdg_directories: dependency: transitive description: @@ -446,5 +446,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index e76a641..e22d64e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A sudoku game application. publish_to: "none" -version: 0.1.22+71 +version: 0.2.0+72 environment: sdk: "^3.0.0" @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - badges: ^3.1.2 + # base easy_localization: ^3.0.1 equatable: ^2.0.5 flutter_bloc: ^8.1.1 @@ -22,14 +22,17 @@ dependencies: path_provider: ^2.0.11 unicons: ^2.1.1 + # specific + badges: ^3.1.2 + dev_dependencies: - flutter_lints: ^3.0.1 + flutter_lints: ^4.0.0 flutter: uses-material-design: true assets: - - assets/icons/ - assets/skins/ + - assets/ui/ - assets/translations/ fonts: @@ -43,3 +46,4 @@ flutter: weight: 400 - asset: assets/fonts/Nunito-Light.ttf weight: 300 + diff --git a/icons/build_application_icons.sh b/resources/app/build_application_resources.sh similarity index 98% rename from icons/build_application_icons.sh rename to resources/app/build_application_resources.sh index 27dbe26..6d67b8f 100755 --- a/icons/build_application_icons.sh +++ b/resources/app/build_application_resources.sh @@ -6,7 +6,7 @@ command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not ins command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; } CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -BASE_DIR="$(dirname "${CURRENT_DIR}")" +BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")" SOURCE_ICON="${CURRENT_DIR}/icon.svg" SOURCE_FASTLANE="${CURRENT_DIR}/featureGraphic.svg" diff --git a/icons/featureGraphic.svg b/resources/app/featureGraphic.svg similarity index 100% rename from icons/featureGraphic.svg rename to resources/app/featureGraphic.svg diff --git a/icons/icon.svg b/resources/app/icon.svg similarity index 100% rename from icons/icon.svg rename to resources/app/icon.svg diff --git a/resources/build_resources.sh b/resources/build_resources.sh new file mode 100755 index 0000000..659697a --- /dev/null +++ b/resources/build_resources.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" + +${CURRENT_DIR}/app/build_application_resources.sh +${CURRENT_DIR}/ui/build_ui_resources.sh + diff --git a/icons/build_game_icons.sh b/resources/ui/build_ui_resources.sh similarity index 54% rename from icons/build_game_icons.sh rename to resources/ui/build_ui_resources.sh index e079a34..4f365ed 100755 --- a/icons/build_game_icons.sh +++ b/resources/ui/build_ui_resources.sh @@ -6,7 +6,7 @@ command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not ins command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; } CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -BASE_DIR="$(dirname "${CURRENT_DIR}")" +BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")" ASSETS_DIR="${BASE_DIR}/assets" OPTIPNG_OPTIONS="-preserve -quiet -o7" @@ -14,46 +14,23 @@ ICON_SIZE=192 ####################################################### -# Game images -AVAILABLE_GAME_IMAGES=" - button_back - button_start - button_resume_game - button_delete_saved_game - button_help - button_show_conflicts - game_win - placeholder - cell_empty -" - -# Skins -AVAILABLE_SKINS=" - digits - food - monsters - nature -" - -# Images per skin -SKIN_IMAGES=" - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 -" +# Game images (svg files found in `images` folder) +AVAILABLE_GAME_IMAGES="" +if [ -d "${CURRENT_DIR}/images" ]; then + AVAILABLE_GAME_IMAGES="$(find "${CURRENT_DIR}/images" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort)" +fi + +# Skins (subfolders found in `skins` folder) +AVAILABLE_SKINS="" +if [ -d "${CURRENT_DIR}/skins" ]; then + AVAILABLE_SKINS="$(find "${CURRENT_DIR}/skins" -mindepth 1 -type d | awk -F/ '{print $NF}')" +fi + +# Images per skin (svg files found recursively in `skins` folder and subfolders) +SKIN_IMAGES="" +if [ -d "${CURRENT_DIR}/skins" ]; then + SKIN_IMAGES="$(find "${CURRENT_DIR}/skins" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort | uniq)" +fi ####################################################### @@ -75,7 +52,7 @@ function optimize_svg() { } # build icons -function build_icon() { +function build_image() { SOURCE="$1" TARGET="$2" @@ -88,46 +65,46 @@ function build_icon() { optimize_svg "${SOURCE}" + mkdir -p "$(dirname "${TARGET}")" + inkscape \ --export-width=${ICON_SIZE} \ --export-height=${ICON_SIZE} \ --export-filename=${TARGET} \ - ${SOURCE} + "${SOURCE}" - optipng ${OPTIPNG_OPTIONS} ${TARGET} + optipng ${OPTIPNG_OPTIONS} "${TARGET}" } -function build_icon_for_skin() { +function build_image_for_skin() { SKIN_CODE="$1" - # skin main image - build_icon ${CURRENT_DIR}/skin_${SKIN_CODE}.svg ${ASSETS_DIR}/icons/skin_${SKIN_CODE}.png - # skin images for SKIN_IMAGE in ${SKIN_IMAGES} do - build_icon ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_IMAGE}.svg ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_IMAGE}.png + build_image ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_IMAGE}.svg ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_IMAGE}.png done } ####################################################### -# Create output folders -mkdir -p ${ASSETS_DIR}/icons -mkdir -p ${ASSETS_DIR}/skins - # Delete existing generated images -find ${ASSETS_DIR}/icons -type f -name "*.png" -delete -find ${ASSETS_DIR}/skins -type f -name "*.png" -delete +if [ -d "${ASSETS_DIR}/ui" ]; then + find ${ASSETS_DIR}/ui -type f -name "*.png" -delete +fi +if [ -d "${ASSETS_DIR}/skins" ]; then + find ${ASSETS_DIR}/skins -type f -name "*.png" -delete +fi # build game images for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES} do - build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png + build_image ${CURRENT_DIR}/images/${GAME_IMAGE}.svg ${ASSETS_DIR}/ui/${GAME_IMAGE}.png done # build skins images for SKIN in ${AVAILABLE_SKINS} do - build_icon_for_skin "${SKIN}" + build_image_for_skin "${SKIN}" done + diff --git a/icons/button_back.svg b/resources/ui/images/button_back.svg similarity index 100% rename from icons/button_back.svg rename to resources/ui/images/button_back.svg diff --git a/icons/button_delete_saved_game.svg b/resources/ui/images/button_delete_saved_game.svg similarity index 100% rename from icons/button_delete_saved_game.svg rename to resources/ui/images/button_delete_saved_game.svg diff --git a/icons/button_help.svg b/resources/ui/images/button_help.svg similarity index 100% rename from icons/button_help.svg rename to resources/ui/images/button_help.svg diff --git a/icons/button_resume_game.svg b/resources/ui/images/button_resume_game.svg similarity index 100% rename from icons/button_resume_game.svg rename to resources/ui/images/button_resume_game.svg diff --git a/icons/button_show_conflicts.svg b/resources/ui/images/button_show_conflicts.svg similarity index 100% rename from icons/button_show_conflicts.svg rename to resources/ui/images/button_show_conflicts.svg diff --git a/icons/button_start.svg b/resources/ui/images/button_start.svg similarity index 100% rename from icons/button_start.svg rename to resources/ui/images/button_start.svg diff --git a/icons/cell_empty.svg b/resources/ui/images/cell_empty.svg similarity index 100% rename from icons/cell_empty.svg rename to resources/ui/images/cell_empty.svg diff --git a/icons/game_win.svg b/resources/ui/images/game_win.svg similarity index 100% rename from icons/game_win.svg rename to resources/ui/images/game_win.svg diff --git a/icons/placeholder.svg b/resources/ui/images/placeholder.svg similarity index 100% rename from icons/placeholder.svg rename to resources/ui/images/placeholder.svg diff --git a/icons/skin_digits.svg b/resources/ui/images/skin_digits.svg similarity index 100% rename from icons/skin_digits.svg rename to resources/ui/images/skin_digits.svg diff --git a/icons/skin_food.svg b/resources/ui/images/skin_food.svg similarity index 100% rename from icons/skin_food.svg rename to resources/ui/images/skin_food.svg diff --git a/icons/skin_monsters.svg b/resources/ui/images/skin_monsters.svg similarity index 100% rename from icons/skin_monsters.svg rename to resources/ui/images/skin_monsters.svg diff --git a/icons/skin_nature.svg b/resources/ui/images/skin_nature.svg similarity index 100% rename from icons/skin_nature.svg rename to resources/ui/images/skin_nature.svg diff --git a/icons/skins/digits/1.svg b/resources/ui/skins/digits/1.svg similarity index 100% rename from icons/skins/digits/1.svg rename to resources/ui/skins/digits/1.svg diff --git a/icons/skins/digits/10.svg b/resources/ui/skins/digits/10.svg similarity index 100% rename from icons/skins/digits/10.svg rename to resources/ui/skins/digits/10.svg diff --git a/icons/skins/digits/11.svg b/resources/ui/skins/digits/11.svg similarity index 100% rename from icons/skins/digits/11.svg rename to resources/ui/skins/digits/11.svg diff --git a/icons/skins/digits/12.svg b/resources/ui/skins/digits/12.svg similarity index 100% rename from icons/skins/digits/12.svg rename to resources/ui/skins/digits/12.svg diff --git a/icons/skins/digits/13.svg b/resources/ui/skins/digits/13.svg similarity index 100% rename from icons/skins/digits/13.svg rename to resources/ui/skins/digits/13.svg diff --git a/icons/skins/digits/14.svg b/resources/ui/skins/digits/14.svg similarity index 100% rename from icons/skins/digits/14.svg rename to resources/ui/skins/digits/14.svg diff --git a/icons/skins/digits/15.svg b/resources/ui/skins/digits/15.svg similarity index 100% rename from icons/skins/digits/15.svg rename to resources/ui/skins/digits/15.svg diff --git a/icons/skins/digits/16.svg b/resources/ui/skins/digits/16.svg similarity index 100% rename from icons/skins/digits/16.svg rename to resources/ui/skins/digits/16.svg diff --git a/icons/skins/digits/2.svg b/resources/ui/skins/digits/2.svg similarity index 100% rename from icons/skins/digits/2.svg rename to resources/ui/skins/digits/2.svg diff --git a/icons/skins/digits/3.svg b/resources/ui/skins/digits/3.svg similarity index 100% rename from icons/skins/digits/3.svg rename to resources/ui/skins/digits/3.svg diff --git a/icons/skins/digits/4.svg b/resources/ui/skins/digits/4.svg similarity index 100% rename from icons/skins/digits/4.svg rename to resources/ui/skins/digits/4.svg diff --git a/icons/skins/digits/5.svg b/resources/ui/skins/digits/5.svg similarity index 100% rename from icons/skins/digits/5.svg rename to resources/ui/skins/digits/5.svg diff --git a/icons/skins/digits/6.svg b/resources/ui/skins/digits/6.svg similarity index 100% rename from icons/skins/digits/6.svg rename to resources/ui/skins/digits/6.svg diff --git a/icons/skins/digits/7.svg b/resources/ui/skins/digits/7.svg similarity index 100% rename from icons/skins/digits/7.svg rename to resources/ui/skins/digits/7.svg diff --git a/icons/skins/digits/8.svg b/resources/ui/skins/digits/8.svg similarity index 100% rename from icons/skins/digits/8.svg rename to resources/ui/skins/digits/8.svg diff --git a/icons/skins/digits/9.svg b/resources/ui/skins/digits/9.svg similarity index 100% rename from icons/skins/digits/9.svg rename to resources/ui/skins/digits/9.svg diff --git a/icons/skins/food/1.svg b/resources/ui/skins/food/1.svg similarity index 100% rename from icons/skins/food/1.svg rename to resources/ui/skins/food/1.svg diff --git a/icons/skins/food/10.svg b/resources/ui/skins/food/10.svg similarity index 100% rename from icons/skins/food/10.svg rename to resources/ui/skins/food/10.svg diff --git a/icons/skins/food/11.svg b/resources/ui/skins/food/11.svg similarity index 100% rename from icons/skins/food/11.svg rename to resources/ui/skins/food/11.svg diff --git a/icons/skins/food/12.svg b/resources/ui/skins/food/12.svg similarity index 100% rename from icons/skins/food/12.svg rename to resources/ui/skins/food/12.svg diff --git a/icons/skins/food/13.svg b/resources/ui/skins/food/13.svg similarity index 100% rename from icons/skins/food/13.svg rename to resources/ui/skins/food/13.svg diff --git a/icons/skins/food/14.svg b/resources/ui/skins/food/14.svg similarity index 100% rename from icons/skins/food/14.svg rename to resources/ui/skins/food/14.svg diff --git a/icons/skins/food/15.svg b/resources/ui/skins/food/15.svg similarity index 100% rename from icons/skins/food/15.svg rename to resources/ui/skins/food/15.svg diff --git a/icons/skins/food/16.svg b/resources/ui/skins/food/16.svg similarity index 100% rename from icons/skins/food/16.svg rename to resources/ui/skins/food/16.svg diff --git a/icons/skins/food/2.svg b/resources/ui/skins/food/2.svg similarity index 100% rename from icons/skins/food/2.svg rename to resources/ui/skins/food/2.svg diff --git a/icons/skins/food/3.svg b/resources/ui/skins/food/3.svg similarity index 100% rename from icons/skins/food/3.svg rename to resources/ui/skins/food/3.svg diff --git a/icons/skins/food/4.svg b/resources/ui/skins/food/4.svg similarity index 100% rename from icons/skins/food/4.svg rename to resources/ui/skins/food/4.svg diff --git a/icons/skins/food/5.svg b/resources/ui/skins/food/5.svg similarity index 100% rename from icons/skins/food/5.svg rename to resources/ui/skins/food/5.svg diff --git a/icons/skins/food/6.svg b/resources/ui/skins/food/6.svg similarity index 100% rename from icons/skins/food/6.svg rename to resources/ui/skins/food/6.svg diff --git a/icons/skins/food/7.svg b/resources/ui/skins/food/7.svg similarity index 100% rename from icons/skins/food/7.svg rename to resources/ui/skins/food/7.svg diff --git a/icons/skins/food/8.svg b/resources/ui/skins/food/8.svg similarity index 100% rename from icons/skins/food/8.svg rename to resources/ui/skins/food/8.svg diff --git a/icons/skins/food/9.svg b/resources/ui/skins/food/9.svg similarity index 100% rename from icons/skins/food/9.svg rename to resources/ui/skins/food/9.svg diff --git a/icons/skins/monsters/1.svg b/resources/ui/skins/monsters/1.svg similarity index 100% rename from icons/skins/monsters/1.svg rename to resources/ui/skins/monsters/1.svg diff --git a/icons/skins/monsters/10.svg b/resources/ui/skins/monsters/10.svg similarity index 100% rename from icons/skins/monsters/10.svg rename to resources/ui/skins/monsters/10.svg diff --git a/icons/skins/monsters/11.svg b/resources/ui/skins/monsters/11.svg similarity index 100% rename from icons/skins/monsters/11.svg rename to resources/ui/skins/monsters/11.svg diff --git a/icons/skins/monsters/12.svg b/resources/ui/skins/monsters/12.svg similarity index 100% rename from icons/skins/monsters/12.svg rename to resources/ui/skins/monsters/12.svg diff --git a/icons/skins/monsters/13.svg b/resources/ui/skins/monsters/13.svg similarity index 100% rename from icons/skins/monsters/13.svg rename to resources/ui/skins/monsters/13.svg diff --git a/icons/skins/monsters/14.svg b/resources/ui/skins/monsters/14.svg similarity index 100% rename from icons/skins/monsters/14.svg rename to resources/ui/skins/monsters/14.svg diff --git a/icons/skins/monsters/15.svg b/resources/ui/skins/monsters/15.svg similarity index 100% rename from icons/skins/monsters/15.svg rename to resources/ui/skins/monsters/15.svg diff --git a/icons/skins/monsters/16.svg b/resources/ui/skins/monsters/16.svg similarity index 100% rename from icons/skins/monsters/16.svg rename to resources/ui/skins/monsters/16.svg diff --git a/icons/skins/monsters/2.svg b/resources/ui/skins/monsters/2.svg similarity index 100% rename from icons/skins/monsters/2.svg rename to resources/ui/skins/monsters/2.svg diff --git a/icons/skins/monsters/3.svg b/resources/ui/skins/monsters/3.svg similarity index 100% rename from icons/skins/monsters/3.svg rename to resources/ui/skins/monsters/3.svg diff --git a/icons/skins/monsters/4.svg b/resources/ui/skins/monsters/4.svg similarity index 100% rename from icons/skins/monsters/4.svg rename to resources/ui/skins/monsters/4.svg diff --git a/icons/skins/monsters/5.svg b/resources/ui/skins/monsters/5.svg similarity index 100% rename from icons/skins/monsters/5.svg rename to resources/ui/skins/monsters/5.svg diff --git a/icons/skins/monsters/6.svg b/resources/ui/skins/monsters/6.svg similarity index 100% rename from icons/skins/monsters/6.svg rename to resources/ui/skins/monsters/6.svg diff --git a/icons/skins/monsters/7.svg b/resources/ui/skins/monsters/7.svg similarity index 100% rename from icons/skins/monsters/7.svg rename to resources/ui/skins/monsters/7.svg diff --git a/icons/skins/monsters/8.svg b/resources/ui/skins/monsters/8.svg similarity index 100% rename from icons/skins/monsters/8.svg rename to resources/ui/skins/monsters/8.svg diff --git a/icons/skins/monsters/9.svg b/resources/ui/skins/monsters/9.svg similarity index 100% rename from icons/skins/monsters/9.svg rename to resources/ui/skins/monsters/9.svg diff --git a/icons/skins/nature/1.svg b/resources/ui/skins/nature/1.svg similarity index 100% rename from icons/skins/nature/1.svg rename to resources/ui/skins/nature/1.svg diff --git a/icons/skins/nature/10.svg b/resources/ui/skins/nature/10.svg similarity index 100% rename from icons/skins/nature/10.svg rename to resources/ui/skins/nature/10.svg diff --git a/icons/skins/nature/11.svg b/resources/ui/skins/nature/11.svg similarity index 100% rename from icons/skins/nature/11.svg rename to resources/ui/skins/nature/11.svg diff --git a/icons/skins/nature/12.svg b/resources/ui/skins/nature/12.svg similarity index 100% rename from icons/skins/nature/12.svg rename to resources/ui/skins/nature/12.svg diff --git a/icons/skins/nature/13.svg b/resources/ui/skins/nature/13.svg similarity index 100% rename from icons/skins/nature/13.svg rename to resources/ui/skins/nature/13.svg diff --git a/icons/skins/nature/14.svg b/resources/ui/skins/nature/14.svg similarity index 100% rename from icons/skins/nature/14.svg rename to resources/ui/skins/nature/14.svg diff --git a/icons/skins/nature/15.svg b/resources/ui/skins/nature/15.svg similarity index 100% rename from icons/skins/nature/15.svg rename to resources/ui/skins/nature/15.svg diff --git a/icons/skins/nature/16.svg b/resources/ui/skins/nature/16.svg similarity index 100% rename from icons/skins/nature/16.svg rename to resources/ui/skins/nature/16.svg diff --git a/icons/skins/nature/2.svg b/resources/ui/skins/nature/2.svg similarity index 100% rename from icons/skins/nature/2.svg rename to resources/ui/skins/nature/2.svg diff --git a/icons/skins/nature/3.svg b/resources/ui/skins/nature/3.svg similarity index 100% rename from icons/skins/nature/3.svg rename to resources/ui/skins/nature/3.svg diff --git a/icons/skins/nature/4.svg b/resources/ui/skins/nature/4.svg similarity index 100% rename from icons/skins/nature/4.svg rename to resources/ui/skins/nature/4.svg diff --git a/icons/skins/nature/5.svg b/resources/ui/skins/nature/5.svg similarity index 100% rename from icons/skins/nature/5.svg rename to resources/ui/skins/nature/5.svg diff --git a/icons/skins/nature/6.svg b/resources/ui/skins/nature/6.svg similarity index 100% rename from icons/skins/nature/6.svg rename to resources/ui/skins/nature/6.svg diff --git a/icons/skins/nature/7.svg b/resources/ui/skins/nature/7.svg similarity index 100% rename from icons/skins/nature/7.svg rename to resources/ui/skins/nature/7.svg diff --git a/icons/skins/nature/8.svg b/resources/ui/skins/nature/8.svg similarity index 100% rename from icons/skins/nature/8.svg rename to resources/ui/skins/nature/8.svg diff --git a/icons/skins/nature/9.svg b/resources/ui/skins/nature/9.svg similarity index 100% rename from icons/skins/nature/9.svg rename to resources/ui/skins/nature/9.svg -- GitLab