diff --git a/android/app/build.gradle b/android/app/build.gradle index 56c75471c2ba92bfccfdc5d0b8b7d6bdef991e21..ba7c075900cf22911bcd631e4cc428a72e2cd6c7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { applicationId "org.benoitharrault.jeweled" - minSdkVersion 16 + minSdkVersion flutter.minSdkVersion targetSdkVersion 30 versionCode appVersionCode.toInteger() versionName appVersionName diff --git a/android/gradle.properties b/android/gradle.properties index 65eed6426393974efb5a056ec44936d42b5ef2a1..4bb5439f682100f8ef4ba80a557fe4f2f0ab14c2 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.0.8 -app.versionCode=8 +app.versionName=0.0.9 +app.versionCode=9 diff --git a/assets/fonts/Nunito-Bold.ttf b/assets/fonts/Nunito-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6519feb781449ebe0015cbc74dfd9e13110fbba9 Binary files /dev/null and b/assets/fonts/Nunito-Bold.ttf differ diff --git a/assets/fonts/Nunito-Light.ttf b/assets/fonts/Nunito-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8a0736c41cd6c2a1225d356bf274de1d0afc3497 Binary files /dev/null and b/assets/fonts/Nunito-Light.ttf differ diff --git a/assets/fonts/Nunito-Medium.ttf b/assets/fonts/Nunito-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..88fccdc0638b6f5d6ac49d9d269dc3d518618ad1 Binary files /dev/null and b/assets/fonts/Nunito-Medium.ttf differ diff --git a/assets/fonts/Nunito-Regular.ttf b/assets/fonts/Nunito-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e7b8375a896ef0cd8e06730a78c84532b377e784 Binary files /dev/null and b/assets/fonts/Nunito-Regular.ttf differ diff --git a/assets/icons/size_medium.png b/assets/icons/boardSize_10.png similarity index 100% rename from assets/icons/size_medium.png rename to assets/icons/boardSize_10.png diff --git a/assets/icons/size_large.png b/assets/icons/boardSize_14.png similarity index 100% rename from assets/icons/size_large.png rename to assets/icons/boardSize_14.png diff --git a/assets/icons/size_extra.png b/assets/icons/boardSize_20.png similarity index 100% rename from assets/icons/size_extra.png rename to assets/icons/boardSize_20.png diff --git a/assets/icons/size_small.png b/assets/icons/boardSize_6.png similarity index 100% rename from assets/icons/size_small.png rename to assets/icons/boardSize_6.png diff --git a/assets/icons/colors_5.png b/assets/icons/colorsCount_5.png similarity index 100% rename from assets/icons/colors_5.png rename to assets/icons/colorsCount_5.png diff --git a/assets/icons/colors_6.png b/assets/icons/colorsCount_6.png similarity index 100% rename from assets/icons/colors_6.png rename to assets/icons/colorsCount_6.png diff --git a/assets/icons/colors_7.png b/assets/icons/colorsCount_7.png similarity index 100% rename from assets/icons/colors_7.png rename to assets/icons/colorsCount_7.png diff --git a/assets/icons/colors_8.png b/assets/icons/colorsCount_8.png similarity index 100% rename from assets/icons/colors_8.png rename to assets/icons/colorsCount_8.png diff --git a/assets/icons/level_easy.png b/assets/icons/level_easy.png deleted file mode 100644 index aa9e2485272c12fd99d9e1faae5d71d027ee5431..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_easy.png and /dev/null differ diff --git a/assets/icons/level_hard.png b/assets/icons/level_hard.png deleted file mode 100644 index 433c686b984fd22c4966def3dedcaf7384951810..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_hard.png and /dev/null differ diff --git a/assets/icons/level_medium.png b/assets/icons/level_medium.png deleted file mode 100644 index 213f2ca844afb0cea47cc4bdd1b865433b068b59..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_medium.png and /dev/null differ diff --git a/assets/icons/level_nightmare.png b/assets/icons/level_nightmare.png deleted file mode 100644 index df4182c1e228bbb33c5546d2df38d16ca1edd4f2..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_nightmare.png and /dev/null differ diff --git a/assets/translations/en.json b/assets/translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..098a873d7b542216c2369f7618a54e5f9ec4e131 --- /dev/null +++ b/assets/translations/en.json @@ -0,0 +1,3 @@ +{ + "app_name": "Jeweled" +} diff --git a/assets/translations/fr.json b/assets/translations/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..098a873d7b542216c2369f7618a54e5f9ec4e131 --- /dev/null +++ b/assets/translations/fr.json @@ -0,0 +1,3 @@ +{ + "app_name": "Jeweled" +} diff --git a/fastlane/metadata/android/en-US/changelogs/9.txt b/fastlane/metadata/android/en-US/changelogs/9.txt new file mode 100644 index 0000000000000000000000000000000000000000..f5fd8646c2688aac7044cb783cc9855ad6a0bc06 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/9.txt @@ -0,0 +1 @@ +Improve game architecture, massive code rewrite. diff --git a/fastlane/metadata/android/fr-FR/changelogs/9.txt b/fastlane/metadata/android/fr-FR/changelogs/9.txt new file mode 100644 index 0000000000000000000000000000000000000000..cb5853858f0262e07da6c8e75a1551e7f55b53cb --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/9.txt @@ -0,0 +1 @@ +Amélioration globale de l'architecture du jeu, réécriture massive de code. diff --git a/icons/size_medium.svg b/icons/boardSize_10.svg similarity index 100% rename from icons/size_medium.svg rename to icons/boardSize_10.svg diff --git a/icons/size_large.svg b/icons/boardSize_14.svg similarity index 100% rename from icons/size_large.svg rename to icons/boardSize_14.svg diff --git a/icons/size_extra.svg b/icons/boardSize_20.svg similarity index 100% rename from icons/size_extra.svg rename to icons/boardSize_20.svg diff --git a/icons/size_small.svg b/icons/boardSize_6.svg similarity index 100% rename from icons/size_small.svg rename to icons/boardSize_6.svg diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh index 08f5571f42f74293ef170e2eac8e7cc59f740670..f86f10bce481975d4de944ff92c06a1d04d5773f 100755 --- a/icons/build_game_icons.sh +++ b/icons/build_game_icons.sh @@ -27,9 +27,8 @@ AVAILABLE_GAME_IMAGES=" # Settings images AVAILABLES_GAME_SETTINGS=" - level:easy,medium,hard,nightmare - size:small,medium,large,extra - colors:5,6,7,8 + boardSize:6,10,14,20 + colorsCount:5,6,7,8 " ####################################################### diff --git a/icons/colors_5.svg b/icons/colorsCount_5.svg similarity index 100% rename from icons/colors_5.svg rename to icons/colorsCount_5.svg diff --git a/icons/colors_6.svg b/icons/colorsCount_6.svg similarity index 100% rename from icons/colors_6.svg rename to icons/colorsCount_6.svg diff --git a/icons/colors_7.svg b/icons/colorsCount_7.svg similarity index 100% rename from icons/colors_7.svg rename to icons/colorsCount_7.svg diff --git a/icons/colors_8.svg b/icons/colorsCount_8.svg similarity index 100% rename from icons/colors_8.svg rename to icons/colorsCount_8.svg diff --git a/icons/level_easy.svg b/icons/level_easy.svg deleted file mode 100644 index 30048ce976f10fba1ad4233115035786a65d35de..0000000000000000000000000000000000000000 --- a/icons/level_easy.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#97c05c" stroke="#000" stroke-width="2"/><path d="m50.952 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#030303" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_hard.svg b/icons/level_hard.svg deleted file mode 100644 index 976249e8b0d2274b791d00e128be518d29a03731..0000000000000000000000000000000000000000 --- a/icons/level_hard.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#cd5542" stroke="#000" stroke-width="2"/><path d="m28.065 11.952c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.839 11.952c1.3622-0.0046 4.9652 11.398 6.07 12.195s13.062 0.61914 13.487 1.9133c0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m50.952 52.835c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80442 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_medium.svg b/icons/level_medium.svg deleted file mode 100644 index e70fd60d179b05a0db5701e4b7858c214887c6be..0000000000000000000000000000000000000000 --- a/icons/level_medium.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#f29c38" stroke="#000" stroke-width="2"/><path d="m27.72 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m74.183 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195s13.062 0.61914 13.487 1.9133-9.3059 8.2444-9.7225 9.5414c-0.41656 1.297 3.4475 12.614 2.3482 13.418-1.0994 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797-1.1048-0.79696 2.6827-12.14 2.2574-13.434s-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_nightmare.svg b/icons/level_nightmare.svg deleted file mode 100644 index 87f28a3defc23c6fe95980cb376efbf56d6885cc..0000000000000000000000000000000000000000 --- a/icons/level_nightmare.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6041b0" stroke="#000" stroke-width="2"/><path d="m28.929 11.793c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797-1.1048-0.79696 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.125 11.861c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80442 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m28.778 52.923c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414s3.4475 12.614 2.3481 13.418c-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797-1.1048-0.79697 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754s12.375-1.2 13.474-2.0044c1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.104 52.992c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797-1.1048-0.79697 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754s12.375-1.2 13.474-2.0044c1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..a8f11fa1d0ae2014a27b912d19ae22efac186a8c --- /dev/null +++ b/lib/config/default_game_settings.dart @@ -0,0 +1,33 @@ +class DefaultGameSettings { + static const List<String> availableParameters = [ + 'boardSize', + 'colorsCount', + ]; + + static const int defaultBoardSizeValue = 10; + static const List<int> allowedBoardSizeValues = [ + 6, + 10, + 14, + 20, + ]; + + static const int defaultColorsCountValue = 6; + static const List<int> allowedColorsCountValues = [ + 5, + 6, + 7, + 8, + ]; + + static List<int> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case 'boardSize': + return DefaultGameSettings.allowedBoardSizeValues; + case 'colorsCount': + return DefaultGameSettings.allowedColorsCountValues; + } + + return []; + } +} diff --git a/lib/config/theme.dart b/lib/config/theme.dart new file mode 100644 index 0000000000000000000000000000000000000000..be390348c7868e7c63387df13e13c46de43f8a23 --- /dev/null +++ b/lib/config/theme.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; + +/// Colors from Tailwind CSS (v3.0) - June 2022 +/// +/// https://tailwindcss.com/docs/customizing-colors + +const int _primaryColor = 0xFF6366F1; +const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{ + 50: Color(0xFFEEF2FF), // indigo-50 + 100: Color(0xFFE0E7FF), // indigo-100 + 200: Color(0xFFC7D2FE), // indigo-200 + 300: Color(0xFFA5B4FC), // indigo-300 + 400: Color(0xFF818CF8), // indigo-400 + 500: Color(_primaryColor), // indigo-500 + 600: Color(0xFF4F46E5), // indigo-600 + 700: Color(0xFF4338CA), // indigo-700 + 800: Color(0xFF3730A3), // indigo-800 + 900: Color(0xFF312E81), // indigo-900 +}); + +const int _textColor = 0xFF64748B; +const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{ + 50: Color(0xFFF8FAFC), // slate-50 + 100: Color(0xFFF1F5F9), // slate-100 + 200: Color(0xFFE2E8F0), // slate-200 + 300: Color(0xFFCBD5E1), // slate-300 + 400: Color(0xFF94A3B8), // slate-400 + 500: Color(_textColor), // slate-500 + 600: Color(0xFF475569), // slate-600 + 700: Color(0xFF334155), // slate-700 + 800: Color(0xFF1E293B), // slate-800 + 900: Color(0xFF0F172A), // slate-900 +}); + +const Color errorColor = Color(0xFFDC2626); // red-600 + +final ColorScheme lightColorScheme = ColorScheme.light( + primary: primarySwatch.shade500, + secondary: primarySwatch.shade500, + onSecondary: Colors.white, + error: errorColor, + background: textSwatch.shade200, + onBackground: textSwatch.shade500, + onSurface: textSwatch.shade500, + surface: textSwatch.shade50, + surfaceVariant: Colors.white, + shadow: textSwatch.shade900.withOpacity(.1), +); + +final ColorScheme darkColorScheme = ColorScheme.dark( + primary: primarySwatch.shade500, + secondary: primarySwatch.shade500, + onSecondary: Colors.white, + error: errorColor, + background: const Color(0xFF171724), + onBackground: textSwatch.shade400, + onSurface: textSwatch.shade300, + surface: const Color(0xFF262630), + surfaceVariant: const Color(0xFF282832), + shadow: textSwatch.shade900.withOpacity(.2), +); + +final ThemeData lightTheme = ThemeData( + colorScheme: lightColorScheme, + fontFamily: 'Nunito', + textTheme: TextTheme( + displayLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + displayMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + displaySmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + headlineLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + headlineMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + headlineSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + titleLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + titleMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + titleSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + bodyLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + bodyMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + bodySmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + labelLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + labelMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + labelSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + ), +); + +final ThemeData darkTheme = lightTheme.copyWith( + colorScheme: darkColorScheme, + textTheme: TextTheme( + displayLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + displayMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + displaySmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + headlineLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + headlineMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + headlineSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + titleLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + titleMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + titleSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + bodyLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + bodyMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + bodySmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + labelLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + labelMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + labelSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + ), +); + +final ThemeData appTheme = darkTheme; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..86e9c01600c0e8acadd57612553ab4fd813f1a18 --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,159 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:jeweled/models/game.dart'; +import 'package:jeweled/models/cell_location.dart'; +import 'package:jeweled/models/game_settings.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() + : super(GameState( + currentGame: Game.createNull(), + )); + + void updateState(Game game) { + emit(GameState( + currentGame: game, + )); + } + + void refresh() { + final Game game = Game( + board: state.currentGame.board, + settings: state.currentGame.settings, + isRunning: state.currentGame.isRunning, + isFinished: state.currentGame.isFinished, + availableBlocksCount: state.currentGame.availableBlocksCount, + movesCount: state.currentGame.movesCount, + score: state.currentGame.score, + ); + // game.dump(); + + updateState(game); + } + + void quitGame() { + state.currentGame.updateGameIsRunning(false); + refresh(); + } + + void updateCellValue(CellLocation locationToUpdate, int? value) { + state.currentGame.updateCellValue(locationToUpdate, value); + refresh(); + } + + void increaseMovesCount() { + this.state.currentGame.increaseMovesCount(); + refresh(); + } + + void increaseScore(int increment) { + this.state.currentGame.increaseScore(increment); + refresh(); + } + + void updateAvailableBlocksCount() { + this.state.currentGame.updateAvailableBlocksCount(); + refresh(); + } + + void updateGameIsFinished(bool gameIsFinished) { + this.state.currentGame.updateGameIsFinished(gameIsFinished); + refresh(); + } + + moveCellsDown() { + final Game currentGame = this.state.currentGame; + + final int boardSizeHorizontal = currentGame.settings.boardSize; + final int boardSizeVertical = currentGame.settings.boardSize; + + for (int row = 0; row < boardSizeVertical; row++) { + for (int col = 0; col < boardSizeHorizontal; col++) { + // empty cell? + if (currentGame.getCellValue(CellLocation.go(row, col)) == null) { + // move cells down + for (int r = row; r > 0; r--) { + this.updateCellValue(CellLocation.go(r, col), + currentGame.getCellValue(CellLocation.go(r - 1, col))); + } + // fill top empty cell + this.updateCellValue( + CellLocation.go(0, col), currentGame.getFillValue(CellLocation.go(row, col))); + } + } + } + } + + void deleteBlock(List<CellLocation> block) { + // Sort cells from top to bottom + block.sort((cell1, cell2) => cell1.row.compareTo(cell2.row)); + // Delete all cells + block.forEach((CellLocation blockItemToDelete) { + this.updateCellValue(blockItemToDelete, null); + }); + // Gravity! + this.moveCellsDown(); + } + + int getScoreFromBlock(int blockSize) { + return 3 * (blockSize - 2); + } + + void tapOnCell(CellLocation tappedCellLocation) { + final Game currentGame = this.state.currentGame; + + final int? cellValue = currentGame.getCellValue(tappedCellLocation); + print('Tap on cell: col=' + + tappedCellLocation.col.toString() + + ' ; row=' + + tappedCellLocation.row.toString() + + ' ; value=' + + cellValue.toString()); + + if (cellValue != null) { + List<CellLocation> block = currentGame.getSiblingCells(tappedCellLocation, []); + print('block size: ' + block.length.toString()); + if (block.length >= 3) { + this.deleteBlock(block); + this.increaseMovesCount(); + this.increaseScore(getScoreFromBlock(block.length)); + this.updateAvailableBlocksCount(); + } + } + + if (!currentGame.hasAtLeastOneAvailableBlock()) { + print('no more block found. finish game.'); + this.updateGameIsFinished(true); + } + } + + void startNewGame(GameSettings settings) { + Game newGame = Game.createNew( + gameSettings: settings, + ); + + newGame.dump(); + + updateState(newGame); + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + Game currentGame = json['currentGame'] as Game; + + return GameState( + currentGame: currentGame, + ); + } + + @override + Map<String, dynamic>? toJson(GameState state) { + return <String, dynamic>{ + 'currentGame': state.currentGame.toJson(), + }; + } +} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..3fd161a0915313722b7a15c55c7cf538a7e3b6e1 --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,19 @@ +part of 'game_cubit.dart'; + +@immutable +class GameState extends Equatable { + const GameState({ + required this.currentGame, + }); + + final Game currentGame; + + @override + List<dynamic> get props => <dynamic>[ + currentGame, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'currentGame': currentGame, + }; +} diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..76b785030f8488d7e61c0486acfaa6ece2b99acf --- /dev/null +++ b/lib/cubit/settings_cubit.dart @@ -0,0 +1,69 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:jeweled/models/game_settings.dart'; + +part 'settings_state.dart'; + +class SettingsCubit extends HydratedCubit<SettingsState> { + SettingsCubit() : super(SettingsState(settings: GameSettings.createDefault())); + + void setValues({ + int? boardSize, + int? colorsCount, + }) { + emit( + SettingsState( + settings: GameSettings( + boardSize: boardSize ?? state.settings.boardSize, + colorsCount: colorsCount ?? state.settings.colorsCount, + ), + ), + ); + } + + int getParameterValue(String code) { + switch (code) { + case 'boardSize': + return GameSettings.getBoardSizeValueFromUnsafe(state.settings.boardSize); + case 'colorsCount': + return GameSettings.getColorsCountValueFromUnsafe(state.settings.colorsCount); + } + return 0; + } + + void setParameterValue(String code, int value) { + print('SettingsCubit.setParameterValue'); + print('code: ' + code + ' / value: ' + value.toString()); + + int boardSize = code == 'boardSize' ? value : getParameterValue('boardSize'); + int colorsCount = code == 'colorsCount' ? value : getParameterValue('colorsCount'); + + setValues( + boardSize: boardSize, + colorsCount: colorsCount, + ); + } + + @override + SettingsState? fromJson(Map<String, dynamic> json) { + int boardSize = json['boardSize'] as int; + int colorsCount = json['colorsCount'] as int; + + return SettingsState( + settings: GameSettings( + boardSize: boardSize, + colorsCount: colorsCount, + ), + ); + } + + @override + Map<String, dynamic>? toJson(SettingsState state) { + return <String, dynamic>{ + 'boardSize': state.settings.boardSize, + 'colorsCount': state.settings.colorsCount, + }; + } +} diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..b7125618710059d16893a9d87e74c6e2565a8603 --- /dev/null +++ b/lib/cubit/settings_state.dart @@ -0,0 +1,19 @@ +part of 'settings_cubit.dart'; + +@immutable +class SettingsState extends Equatable { + const SettingsState({ + required this.settings, + }); + + final GameSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/entities/cell.dart b/lib/entities/cell.dart deleted file mode 100644 index 669d9fe59e12eaeea807baf314a941f7056c2ee2..0000000000000000000000000000000000000000 --- a/lib/entities/cell.dart +++ /dev/null @@ -1,7 +0,0 @@ -class Cell { - String value = '0'; - - Cell( - this.value, - ); -} diff --git a/lib/layout/board.dart b/lib/layout/board.dart deleted file mode 100644 index 4cf8f3c5b276527a10b54623c4eab7bda61c8a47..0000000000000000000000000000000000000000 --- a/lib/layout/board.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jeweled_game/layout/board_painter.dart'; -import 'package:jeweled_game/provider/data.dart'; -import 'package:jeweled_game/utils/board_utils.dart'; - -class Board { - static Container buildGameBoard(Data myProvider, double boardWidth) { - return Container( - margin: EdgeInsets.all(4), - padding: EdgeInsets.all(4), - child: Column( - children: [ - Container( - child: Center( - child: GestureDetector( - onTapUp: (details) { - double xTap = details.localPosition.dx; - double yTap = details.localPosition.dy; - int col = xTap ~/ (boardWidth / myProvider.sizeHorizontal); - int row = yTap ~/ (boardWidth / myProvider.sizeVertical); - BoardUtils.tapOnCell(myProvider, row, col); - }, - child: Container( - child: CustomPaint( - size: Size(boardWidth, boardWidth), - willChange: false, - painter: BoardPainter(myProvider), - isComplex: true, - ), - ), - ), - ), - ) - ], - ), - ); - } -} diff --git a/lib/layout/board_painter.dart b/lib/layout/board_painter.dart deleted file mode 100644 index a758d3785b3ff877b02437d2c96fe8a2a749665f..0000000000000000000000000000000000000000 --- a/lib/layout/board_painter.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:jeweled_game/entities/cell.dart'; -import 'package:jeweled_game/layout/color_theme.dart'; -import 'package:jeweled_game/provider/data.dart'; - -class BoardPainter extends CustomPainter { - const BoardPainter(this.myProvider); - - final Data myProvider; - - @override - void paint(Canvas canvas, Size size) { - int sizeHorizontal = myProvider.sizeHorizontal; - int sizeVertical = myProvider.sizeVertical; - - List cells = myProvider.cells; - double cellSize = size.width / (max(sizeHorizontal, sizeVertical)); - - // background - for (var row = 0; row < sizeVertical; row++) { - double y = cellSize * row; - for (var col = 0; col < sizeHorizontal; col++) { - double x = cellSize * col; - - final Cell cell = cells[row][col]; - final String cellValue = cell.value; - final int colorCode = ColorTheme.getColorCode(myProvider.parameterSkin, cellValue); - - final cellPaintBackground = Paint(); - cellPaintBackground.color = Color(colorCode); - cellPaintBackground.style = PaintingStyle.fill; - - final Rect cellBackground = - Rect.fromPoints(Offset(x - 1, y - 1), Offset(x + cellSize + 2, y + cellSize + 2)); - - canvas.drawRect(cellBackground, cellPaintBackground); - } - } - - // borders - double borderSize = 4; - final cellPaintBorder = Paint(); - cellPaintBorder.color = Colors.black; - cellPaintBorder.strokeWidth = borderSize; - cellPaintBorder.strokeCap = StrokeCap.round; - - for (var row = 0; row < sizeVertical; row++) { - double y = cellSize * row; - for (var col = 0; col < sizeHorizontal; col++) { - double x = cellSize * col; - - final Cell cell = cells[row][col]; - final String cellValue = cell.value; - - if ((row == 0) || (row > 1 && cellValue != myProvider.getCellValue(row - 1, col))) { - Offset borderStart = Offset(x, y); - Offset borderStop = Offset(x + cellSize, y); - canvas.drawLine(borderStart, borderStop, cellPaintBorder); - } - if ((row == sizeVertical - 1) || - ((row + 1) < sizeVertical && cellValue != myProvider.getCellValue(row + 1, col))) { - Offset borderStart = Offset(x, y + cellSize); - Offset borderStop = Offset(x + cellSize, y + cellSize); - canvas.drawLine(borderStart, borderStop, cellPaintBorder); - } - if ((col == 0) || (col > 1 && cellValue != myProvider.getCellValue(row, col - 1))) { - Offset borderStart = Offset(x, y); - Offset borderStop = Offset(x, y + cellSize); - canvas.drawLine(borderStart, borderStop, cellPaintBorder); - } - if ((col == sizeHorizontal - 1) || - ((col + 1) < sizeHorizontal && - cellValue != myProvider.getCellValue(row, col + 1))) { - Offset borderStart = Offset(x + cellSize, y); - Offset borderStop = Offset(x + cellSize, y + cellSize); - canvas.drawLine(borderStart, borderStop, cellPaintBorder); - } - } - } - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - return false; - } -} diff --git a/lib/layout/color_theme.dart b/lib/layout/color_theme.dart deleted file mode 100644 index ebc2309c91c0eea47a3827f328f4e07cb4e69503..0000000000000000000000000000000000000000 --- a/lib/layout/color_theme.dart +++ /dev/null @@ -1,26 +0,0 @@ -class ColorTheme { - static Map<String, Map<String, int>> borderColors = { - 'default': { - '0': 0xffffff, - '1': 0xe63a3f, - '2': 0x708cfd, - '3': 0x359c35, - '4': 0xffce2c, - '5': 0xff6f43, - '6': 0xa13cb1, - '7': 0x38ffff, - '8': 0xf2739d, - }, - }; - static int defaultBorderColor = 0x808080; - - static int getColorCode(String skin, String value) { - if (borderColors.containsKey(skin) && null != borderColors[skin]) { - Map<String, int>? skinColors = borderColors[skin]; - if (null != skinColors && skinColors.containsKey(value) && null != skinColors[value]) { - return (skinColors[value] ?? defaultBorderColor) | 0xFF000000; - } - } - return defaultBorderColor | 0xFF000000; - } -} diff --git a/lib/layout/game.dart b/lib/layout/game.dart deleted file mode 100644 index 4360e6a29564252b57fb47dd05f76814224f849f..0000000000000000000000000000000000000000 --- a/lib/layout/game.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jeweled_game/layout/board.dart'; -import 'package:jeweled_game/provider/data.dart'; -import 'package:jeweled_game/utils/game_utils.dart'; - -class Game { - static Container buildGameWidget(Data myProvider, double boardWidth) { - bool gameIsFinished = myProvider.isGameFinished; - - return Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: 8), - Game.buildTopIndicatorWidget(myProvider), - SizedBox(height: 2), - Expanded( - child: Board.buildGameBoard(myProvider, boardWidth), - ), - SizedBox(height: 2), - Container( - child: gameIsFinished ? Game.buildEndGameMessage(myProvider) : SizedBox(height: 2), - ), - ], - ), - ); - } - - static Widget buildTopIndicatorWidget(Data myProvider) { - return Table( - children: [ - TableRow( - children: [ - Column( - children: [ - Text( - myProvider.score.toString(), - style: TextStyle( - fontSize: 40, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - Text( - myProvider.movesCount.toString(), - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Colors.grey, - ), - ), - ], - ), - Column( - children: [ - Text( - myProvider.availableBlocksCount.toString(), - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.green, - ), - ), - ], - ), - ], - ), - ], - ); - } - - static TextButton buildQuitGameButton(Data myProvider) { - return TextButton( - child: Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () => GameUtils.quitGame(myProvider), - ); - } - - static Container buildEndGameMessage(Data myProvider) { - String decorationImageAssetName = 'assets/icons/game_fail.png'; - - Widget decorationWidget = TextButton( - child: Image( - image: AssetImage(decorationImageAssetName), - fit: BoxFit.fill, - ), - onPressed: () => null, - ); - - return Container( - margin: EdgeInsets.all(2), - padding: EdgeInsets.all(2), - child: Table( - defaultColumnWidth: IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Column( - children: [decorationWidget], - ), - Column( - children: [buildQuitGameButton(myProvider)], - ), - Column( - children: [decorationWidget], - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/layout/parameters.dart b/lib/layout/parameters.dart deleted file mode 100644 index c1f39ad01c7e0b8fc623d2e6e362ded3f0b39fde..0000000000000000000000000000000000000000 --- a/lib/layout/parameters.dart +++ /dev/null @@ -1,184 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jeweled_game/provider/data.dart'; -import 'package:jeweled_game/utils/game_utils.dart'; - -class Parameters { - static double separatorHeight = 2.0; - static double blockMargin = 3.0; - static double blockPadding = 2.0; - static Color buttonBackgroundColor = Colors.white; - static Color buttonBorderColorActive = Colors.blue; - static Color buttonBorderColorInactive = Colors.white; - static double buttonBorderWidth = 10.0; - static double buttonBorderRadius = 8.0; - static double buttonPadding = 0.0; - static double buttonMargin = 0.0; - - static Container buildParametersSelector(Data myProvider) { - List<Widget> lines = []; - - List parameters = myProvider.availableParameters; - for (var index = 0; index < parameters.length; index++) { - lines.add(buildParameterSelector(myProvider, parameters[index])); - lines.add(SizedBox(height: separatorHeight)); - } - - myProvider.loadCurrentSavedState(); - Widget buttonsBlock = myProvider.hasCurrentSavedState() - ? buildResumeGameButton(myProvider) - : buildStartNewGameButton(myProvider); - - return Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: separatorHeight), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: lines, - ), - ), - SizedBox(height: separatorHeight), - Container( - child: buttonsBlock, - ), - ], - ), - ); - } - - static Image buildImageWidget(String imageAssetCode) { - return Image( - image: AssetImage('assets/icons/' + imageAssetCode + '.png'), - fit: BoxFit.fill, - ); - } - - static Container buildImageContainerWidget(String imageAssetCode) { - return Container( - child: buildImageWidget(imageAssetCode), - ); - } - - static Column buildDecorationImageWidget() { - return Column( - children: [ - TextButton( - child: buildImageContainerWidget('placeholder'), - onPressed: () => null, - ), - ], - ); - } - - static Container buildStartNewGameButton(Data myProvider) { - return Container( - margin: EdgeInsets.all(blockMargin), - padding: EdgeInsets.all(blockPadding), - child: Table( - defaultColumnWidth: IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - buildDecorationImageWidget(), - Column( - children: [ - TextButton( - child: buildImageContainerWidget('button_start'), - onPressed: () => GameUtils.startNewGame(myProvider), - ), - ], - ), - buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } - - static Container buildResumeGameButton(Data myProvider) { - return Container( - margin: EdgeInsets.all(blockMargin), - padding: EdgeInsets.all(blockPadding), - child: Table( - defaultColumnWidth: IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Column( - children: [ - TextButton( - child: buildImageContainerWidget('button_delete_saved_game'), - onPressed: () => GameUtils.deleteSavedGame(myProvider), - ), - ], - ), - Column( - children: [ - TextButton( - child: buildImageContainerWidget('button_resume_game'), - onPressed: () => GameUtils.resumeSavedGame(myProvider), - ), - ], - ), - buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } - - static Widget buildParameterSelector(Data myProvider, String parameterCode) { - List availableValues = myProvider.getParameterAvailableValues(parameterCode); - - if (availableValues.length == 1) { - return SizedBox(height: 0.0); - } - - return Table( - defaultColumnWidth: IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - for (var index = 0; index < availableValues.length; index++) - Column( - children: [ - _buildParameterButton(myProvider, parameterCode, availableValues[index]) - ], - ), - ], - ), - ], - ); - } - - static Widget _buildParameterButton( - Data myProvider, String parameterCode, String parameterValue) { - String currentValue = myProvider.getParameterValue(parameterCode).toString(); - - bool isActive = (parameterValue == currentValue); - String imageAsset = parameterCode + '_' + parameterValue; - - return TextButton( - child: Container( - margin: EdgeInsets.all(buttonMargin), - padding: EdgeInsets.all(buttonPadding), - decoration: BoxDecoration( - color: buttonBackgroundColor, - borderRadius: BorderRadius.circular(buttonBorderRadius), - border: Border.all( - color: isActive ? buttonBorderColorActive : buttonBorderColorInactive, - width: buttonBorderWidth, - ), - ), - child: buildImageWidget(imageAsset), - ), - onPressed: () => myProvider.setParameterValue(parameterCode, parameterValue), - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index 683f95f0d7a9eabae68944ae5f366701c50cf32f..df1d2ede98d81dc85c3f60fd54d0c5f96c48fd82 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,36 +1,62 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:jeweled_game/provider/data.dart'; -import 'package:jeweled_game/screens/home.dart'; -import 'package:provider/provider.dart'; -import 'package:overlay_support/overlay_support.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hive/hive.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:path_provider/path_provider.dart'; + +import 'package:jeweled/config/theme.dart'; +import 'package:jeweled/cubit/settings_cubit.dart'; +import 'package:jeweled/cubit/game_cubit.dart'; +import 'package:jeweled/ui/skeleton.dart'; -void main() { +void main() async { + /// Initialize packages WidgetsFlutterBinding.ensureInitialized(); - SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) - .then((value) => runApp(MyApp())); + await EasyLocalization.ensureInitialized(); + final Directory tmpDir = await getTemporaryDirectory(); + Hive.init(tmpDir.toString()); + HydratedBloc.storage = await HydratedStorage.build( + storageDirectory: tmpDir, + ); + + runApp( + EasyLocalization( + path: 'assets/translations', + supportedLocales: const <Locale>[ + Locale('en'), + Locale('fr'), + ], + fallbackLocale: const Locale('en'), + useFallbackTranslations: true, + child: const MyApp(), + ), + ); } class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>(builder: (context, data, child) { - return OverlaySupport( - child: MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - primaryColor: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: Home(), - routes: { - Home.id: (context) => Home(), - }, - ), - ); - }), + return MultiBlocProvider( + providers: [ + BlocProvider<GameCubit>(create: (context) => GameCubit()), + BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), + ], + child: MaterialApp( + title: 'Jeweled', + theme: appTheme, + home: const SkeletonScreen(), + + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, + ), ); } } diff --git a/lib/models/cell_location.dart b/lib/models/cell_location.dart new file mode 100644 index 0000000000000000000000000000000000000000..b7a70ffd791c385899cb582eabcc664bd163ad74 --- /dev/null +++ b/lib/models/cell_location.dart @@ -0,0 +1,13 @@ +class CellLocation { + final int col; + final int row; + + CellLocation({ + required this.col, + required this.row, + }); + + factory CellLocation.go(int row, int col) { + return new CellLocation(col: col, row: row); + } +} diff --git a/lib/models/game.dart b/lib/models/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..90426df03f00d80d8082dcdb98f95c868328dd3d --- /dev/null +++ b/lib/models/game.dart @@ -0,0 +1,266 @@ +import 'dart:math'; + +import 'package:jeweled/models/game_board.dart'; +import 'package:jeweled/models/game_cell.dart'; +import 'package:jeweled/models/cell_location.dart'; +import 'package:jeweled/models/game_settings.dart'; + +class Game { + final GameBoard board; + final GameSettings settings; + bool isRunning = false; + bool isFinished = false; + int availableBlocksCount = 0; + int movesCount = 0; + int score = 0; + + Game({ + required this.board, + required this.settings, + this.isRunning = false, + this.isFinished = false, + this.availableBlocksCount = 0, + this.movesCount = 0, + this.score = 0, + }); + + factory Game.createNull() { + return Game( + board: GameBoard.createNull(), + settings: GameSettings.createDefault(), + ); + } + + factory Game.createNew({GameSettings? gameSettings}) { + GameSettings settings = gameSettings ?? GameSettings.createDefault(); + + return Game( + board: GameBoard.createRandom(settings), + settings: settings, + isRunning: true, + ); + } + + GameCell getCell(CellLocation cellLocation) { + return this.board.cells[cellLocation.row][cellLocation.col]; + } + + int? getCellValue(CellLocation cellLocation) { + return this.getCell(cellLocation).value; + } + + void updateCellValue(CellLocation locationToUpdate, int? value) { + this.board.cells[locationToUpdate.row][locationToUpdate.col].value = value; + } + + void increaseMovesCount() { + this.movesCount += 1; + } + + void increaseScore(int? count) { + this.score += (count ?? 0); + } + + void updateAvailableBlocksCount() { + this.availableBlocksCount = this.getAvailableBlocks(this).length; + } + + void updateGameIsRunning(bool gameIsRunning) { + this.isRunning = gameIsRunning; + } + + void updateGameIsFinished(bool gameIsFinished) { + this.isFinished = gameIsFinished; + } + + List<CellLocation> getSiblingCells( + final CellLocation referenceCellLocation, + List<CellLocation> siblingCells, + ) { + final int boardSizeHorizontal = this.settings.boardSize; + final int boardSizeVertical = this.settings.boardSize; + + final int? referenceValue = this.getCellValue(referenceCellLocation); + + for (var deltaRow = -1; deltaRow <= 1; deltaRow++) { + for (var deltaCol = -1; deltaCol <= 1; deltaCol++) { + if (deltaCol == 0 || deltaRow == 0) { + final int candidateRow = referenceCellLocation.row + deltaRow; + final int candidateCol = referenceCellLocation.col + deltaCol; + + if ((candidateRow >= 0 && candidateRow < boardSizeVertical) && + (candidateCol >= 0 && candidateCol < boardSizeHorizontal)) { + final candidateLocation = CellLocation.go(candidateRow, candidateCol); + + if (this.getCellValue(candidateLocation) == referenceValue) { + bool alreadyFound = false; + for (var index = 0; index < siblingCells.length; index++) { + if ((siblingCells[index].row == candidateRow) && + (siblingCells[index].col == candidateCol)) { + alreadyFound = true; + } + } + if (!alreadyFound) { + siblingCells.add(candidateLocation); + siblingCells = getSiblingCells(candidateLocation, siblingCells); + } + } + } + } + } + } + + return siblingCells; + } + + List<List<CellLocation>> getAvailableBlocks(final Game game) { + final int boardSizeHorizontal = game.settings.boardSize; + final int boardSizeVertical = game.settings.boardSize; + + final List<List<CellLocation>> blocks = []; + + for (var row = 0; row < boardSizeVertical; row++) { + for (var col = 0; col < boardSizeHorizontal; col++) { + final CellLocation cellLocation = CellLocation.go(row, col); + if (game.getCellValue(cellLocation) != null) { + // if current cell not already in a found block + bool alreadyFound = false; + + blocks.forEach((List<CellLocation> foundBlock) { + foundBlock.forEach((CellLocation foundBlockCell) { + if ((foundBlockCell.row == row) && (foundBlockCell.col == col)) { + alreadyFound = true; + } + }); + }); + if (!alreadyFound) { + final List<CellLocation> block = game.getSiblingCells(cellLocation, []); + if (block.length >= 3) { + blocks.add(block); + } + } + } + } + } + + return blocks; + } + + bool hasAtLeastOneAvailableBlock() { + final int boardSizeHorizontal = this.settings.boardSize; + final int boardSizeVertical = this.settings.boardSize; + + for (var row = 0; row < boardSizeVertical; row++) { + for (var col = 0; col < boardSizeHorizontal; col++) { + final CellLocation cellLocation = CellLocation.go(row, col); + if (this.getCellValue(cellLocation) != null) { + final List<CellLocation> block = this.getSiblingCells(cellLocation, []); + if (block.length >= 3) { + // found one block => ok, not locked + return true; + } + } + } + } + + print('Board is locked!'); + return false; + } + + bool isInBoard(CellLocation cell) { + if (cell.row > 0 && + cell.row < this.settings.boardSize && + cell.col > 0 && + cell.col < this.settings.boardSize) { + return true; + } + return false; + } + + int getFillValue(CellLocation referenceCellLocation) { + final int row = referenceCellLocation.row; + final int col = referenceCellLocation.col; + + // build a list of values to pick one + final List<int> values = []; + + // All eligible values + final int maxValue = this.settings.colorsCount; + for (int i = 1; i <= maxValue; i++) { + values.add(i); + } + + // Add values of current col + for (int r = 0; r <= this.settings.boardSize; r++) { + if (this.isInBoard(CellLocation.go(r, col))) { + final int? value = this.getCellValue(CellLocation.go(r, col)); + if (value != null) { + values.add(value); + } + } + } + + // Add values of sibling cols + for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { + final int c = col + deltaCol; + for (int r = 0; r < this.settings.boardSize; r++) { + if (this.isInBoard(CellLocation.go(r, c))) { + final int? value = this.getCellValue(CellLocation.go(r, c)); + if (value != null) { + values.add(value); + } + } + } + } + + // Add values of sibling cells + for (int deltaCol = -2; deltaCol <= 2; deltaCol++) { + final int c = col + deltaCol; + for (int deltaRow = -2; deltaRow <= 2; deltaRow++) { + final int r = row + deltaRow; + if (this.isInBoard(CellLocation.go(r, c))) { + final int? value = this.getCellValue(CellLocation.go(r, c)); + if (value != null) { + values.add(value); + } + } + } + } + + // Pick random value from "ponderated" list + return values[Random().nextInt(values.length)]; + } + + void dump() { + print(''); + print('## Current game dump:'); + print(''); + this.settings.dump(); + print(''); + this.board.dump(); + print(''); + print('Game: '); + print(' isRunning: ' + isRunning.toString()); + print(' isFinished: ' + isFinished.toString()); + print(' movesCount: ' + movesCount.toString()); + print(' score: ' + score.toString()); + print(' availableBlocksCount: ' + availableBlocksCount.toString()); + print(''); + } + + String toString() { + return 'Game(' + this.toJson().toString() + ')'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'board': this.board.toJson(), + 'settings': this.settings.toJson(), + 'isRunning': this.isRunning, + 'isFinished': this.isFinished, + 'availableBlocksCount': this.availableBlocksCount, + 'movesCount': this.movesCount, + 'score': this.score, + }; + } +} diff --git a/lib/models/game_board.dart b/lib/models/game_board.dart new file mode 100644 index 0000000000000000000000000000000000000000..bea21887bc25242d69c813194674ac15d1b8092e --- /dev/null +++ b/lib/models/game_board.dart @@ -0,0 +1,70 @@ +import 'dart:math'; + +import 'package:jeweled/models/game_cell.dart'; +import 'package:jeweled/models/game_settings.dart'; + +class GameBoard { + final List<List<GameCell>> cells; + + GameBoard({ + required this.cells, + }); + + factory GameBoard.createNull() { + return GameBoard(cells: []); + } + + factory GameBoard.createRandom(GameSettings gameSettings) { + final int boardSizeHorizontal = gameSettings.boardSize; + final int boardSizeVertical = gameSettings.boardSize; + final int maxValue = gameSettings.colorsCount; + + final rand = new Random(); + + List<List<GameCell>> cells = []; + for (var rowIndex = 0; rowIndex < boardSizeVertical; rowIndex++) { + List<GameCell> row = []; + for (var colIndex = 0; colIndex < boardSizeHorizontal; colIndex++) { + int value = 1 + rand.nextInt(maxValue); + row.add(GameCell(value)); + } + cells.add(row); + } + + return GameBoard( + cells: cells, + ); + } + + void dump() { + String horizontalRule = '----'; + cells[0].forEach((element) { + horizontalRule += '-'; + }); + + print('Board:'); + print(horizontalRule); + + for (var rowIndex = 0; rowIndex < cells.length; rowIndex++) { + String row = '| '; + for (var colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + row += cells[rowIndex][colIndex].value.toString(); + } + row += ' |'; + + print(row); + } + + print(horizontalRule); + } + + String toString() { + return 'Board(' + this.toJson().toString() + ')'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'cells': this.cells.toString(), + }; + } +} diff --git a/lib/models/game_cell.dart b/lib/models/game_cell.dart new file mode 100644 index 0000000000000000000000000000000000000000..25667ccf76bfbd4ef65f8651c120a7dbaab641b8 --- /dev/null +++ b/lib/models/game_cell.dart @@ -0,0 +1,17 @@ +class GameCell { + int? value; + + GameCell( + this.value, + ); + + String toString() { + return 'Cell(' + this.toJson().toString() + ')'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'value': this.value, + }; + } +} diff --git a/lib/models/game_settings.dart b/lib/models/game_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..6e2cb12098401e1187ff7d5afc89928d9cf8a4be --- /dev/null +++ b/lib/models/game_settings.dart @@ -0,0 +1,51 @@ +import 'package:jeweled/config/default_game_settings.dart'; + +class GameSettings { + final int boardSize; + final int colorsCount; + + GameSettings({ + required this.boardSize, + required this.colorsCount, + }); + + static int getBoardSizeValueFromUnsafe(int size) { + if (DefaultGameSettings.allowedBoardSizeValues.contains(size)) { + return size; + } + + return DefaultGameSettings.defaultBoardSizeValue; + } + + static int getColorsCountValueFromUnsafe(int colorsCount) { + if (DefaultGameSettings.allowedColorsCountValues.contains(colorsCount)) { + return colorsCount; + } + + return DefaultGameSettings.defaultColorsCountValue; + } + + factory GameSettings.createDefault() { + return GameSettings( + boardSize: DefaultGameSettings.defaultBoardSizeValue, + colorsCount: DefaultGameSettings.defaultColorsCountValue, + ); + } + + void dump() { + print('Settings: '); + print(' boardSize: ' + boardSize.toString()); + print(' colorsCount: ' + colorsCount.toString()); + } + + String toString() { + return 'GameSettings(' + this.toJson().toString() + ')'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'boardSize': this.boardSize, + 'colorsCount': this.colorsCount, + }; + } +} diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index c07515902c44a6bfe3bed62ec778cdf58e389cd1..0000000000000000000000000000000000000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,320 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:jeweled_game/entities/cell.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class Data extends ChangeNotifier { - // Configuration available parameters - List _availableParameters = ['level', 'size', 'colors', 'skin']; - - List _availableLevelValues = ['easy', 'medium', 'hard', 'nightmare']; - List _availableSizeValues = ['small', 'medium', 'large', 'extra']; - List _availableColorsValues = ['5', '6', '7', '8']; - List _availableSkinValues = ['default']; - - List get availableParameters => _availableParameters; - List get availableLevelValues => _availableLevelValues; - List get availableSizeValues => _availableSizeValues; - List get availableSkinValues => _availableSkinValues; - - // Application default configuration - String _parameterLevel = ''; - String _parameterLevelDefault = 'medium'; - String _parameterSize = ''; - String _parameterSizeDefault = 'medium'; - String _parameterColors = ''; - String _parameterColorsDefault = '6'; - String _parameterSkin = ''; - String _parameterSkinDefault = 'default'; - - // Application current configuration - String get parameterLevel => _parameterLevel; - String get parameterSize => _parameterSize; - String get parameterSkin => _parameterSkin; - - // Game data - bool _gameIsRunning = false; - bool _gameIsFinished = false; - int _sizeVertical = 0; - int _sizeHorizontal = 0; - int _colorsCount = 0; - List<List<Cell>> _cells = []; - String _currentState = ''; - int _availableBlocksCount = 0; - int _score = 0; - int _movesCount = 0; - - void updateParameterLevel(String parameterLevel) { - _parameterLevel = parameterLevel; - notifyListeners(); - } - - int get sizeVertical => _sizeVertical; - int get sizeHorizontal => _sizeHorizontal; - void updateParameterSize(String parameterSize) { - _parameterSize = parameterSize; - updateBoardSize(getBoardSizeFromParameter(parameterSize)); - notifyListeners(); - } - - void updateParameterColors(String parameterColors) { - _parameterColors = parameterColors; - updateColorsCount(getColorsCountFromParameter(parameterColors)); - notifyListeners(); - } - - void updateParameterSkin(String parameterSkin) { - _parameterSkin = parameterSkin; - notifyListeners(); - } - - String getParameterValue(String parameterCode) { - switch (parameterCode) { - case 'level': - { - return _parameterLevel; - } - case 'size': - { - return _parameterSize; - } - case 'colors': - { - return _parameterColors; - } - case 'skin': - { - return _parameterSkin; - } - } - return ''; - } - - List getParameterAvailableValues(String parameterCode) { - switch (parameterCode) { - case 'level': - { - return _availableLevelValues; - } - case 'size': - { - return _availableSizeValues; - } - case 'colors': - { - return _availableColorsValues; - } - case 'skin': - { - return _availableSkinValues; - } - } - return []; - } - - void setParameterValue(String parameterCode, String parameterValue) async { - switch (parameterCode) { - case 'level': - { - updateParameterLevel(parameterValue); - } - break; - case 'size': - { - updateParameterSize(parameterValue); - } - break; - case 'colors': - { - updateParameterColors(parameterValue); - } - break; - case 'skin': - { - updateParameterSkin(parameterValue); - } - break; - } - final prefs = await SharedPreferences.getInstance(); - prefs.setString(parameterCode, parameterValue); - } - - void initParametersValues() async { - final prefs = await SharedPreferences.getInstance(); - setParameterValue('level', prefs.getString('level') ?? _parameterLevelDefault); - setParameterValue('size', prefs.getString('size') ?? _parameterSizeDefault); - setParameterValue('colors', prefs.getString('colors') ?? _parameterColorsDefault); - setParameterValue('skin', prefs.getString('skin') ?? _parameterSkinDefault); - } - - String getBoardSizeFromParameter(String parameterSize) { - switch (parameterSize) { - case 'small': - { - return '6x6'; - } - case 'medium': - { - return '10x10'; - } - case 'large': - { - return '14x14'; - } - case 'extra': - { - return '20x20'; - } - } - return getBoardSizeFromParameter(_parameterSizeDefault); - } - - int getColorsCountFromParameter(String parameterColors) { - switch (parameterColors) { - case '5': - { - return 5; - } - case '6': - { - return 6; - } - case '7': - { - return 7; - } - case '8': - { - return 8; - } - } - return getColorsCountFromParameter(_parameterColorsDefault); - } - - void updateBoardSize(String boardSize) { - _sizeHorizontal = int.parse(boardSize.split('x')[0]); - _sizeVertical = int.parse(boardSize.split('x')[1]); - } - - int get colorsCount => _colorsCount; - void updateColorsCount(int colorsCount) { - _colorsCount = colorsCount; - } - - String get currentState => _currentState; - - String computeCurrentGameState() { - String cellsValues = ''; - for (var rowIndex = 0; rowIndex < _cells.length; rowIndex++) { - for (var colIndex = 0; colIndex < _cells[rowIndex].length; colIndex++) { - cellsValues += _cells[rowIndex][colIndex].value; - } - } - - var currentState = { - 'level': _parameterLevel, - 'size': _parameterSize, - 'skin': _parameterSkin, - 'board': cellsValues, - }; - - return json.encode(currentState); - } - - void saveCurrentGameState() async { - if (_gameIsRunning) { - _currentState = computeCurrentGameState(); - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('savedState', _currentState); - } else { - resetCurrentSavedState(); - } - } - - void resetCurrentSavedState() async { - _currentState = ''; - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('savedState', _currentState); - notifyListeners(); - } - - void loadCurrentSavedState() async { - final prefs = await SharedPreferences.getInstance(); - _currentState = prefs.getString('savedState') ?? ''; - } - - bool hasCurrentSavedState() { - return (_currentState != ''); - } - - Map<String, dynamic> getCurrentSavedState() { - if (_currentState != '') { - Map<String, dynamic> savedState = json.decode(_currentState); - if (savedState.isNotEmpty) { - return savedState; - } - } - return {}; - } - - List<List<Cell>> get cells => _cells; - void updateCells(List<List<Cell>> cells) { - _cells = cells; - notifyListeners(); - } - - Cell getCell(int row, int col) { - return cells[row][col]; - } - - String getCellValue(int row, int col) { - return getCell(row, col).value; - } - - updateCellValue(int col, int row, String value) { - _cells[row][col].value = value; - notifyListeners(); - } - - bool get isGameRunning => _gameIsRunning; - void updateGameIsRunning(bool gameIsRunning) { - _gameIsRunning = gameIsRunning; - notifyListeners(); - } - - bool get isGameFinished => _gameIsFinished; - void updateGameIsFinished(bool gameIsFinished) { - _gameIsFinished = gameIsFinished; - notifyListeners(); - } - - int get availableBlocksCount => _availableBlocksCount; - void updateAvailableBlocksCount(int availableBlocksCount) { - _availableBlocksCount = availableBlocksCount; - notifyListeners(); - } - - int get score => _score; - void increaseScore(int increment) { - _score += increment; - notifyListeners(); - } - - int get movesCount => _movesCount; - void increaseMovesCount() { - _movesCount++; - notifyListeners(); - } - - void resetGame() { - _gameIsRunning = false; - _gameIsFinished = false; - _availableBlocksCount = 0; - _score = 0; - _movesCount = 0; - notifyListeners(); - } -} diff --git a/lib/screens/home.dart b/lib/screens/home.dart deleted file mode 100644 index 6db733efe8f6c85a698d18edd50483590afdd585..0000000000000000000000000000000000000000 --- a/lib/screens/home.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jeweled_game/layout/game.dart'; -import 'package:jeweled_game/layout/parameters.dart'; -import 'package:jeweled_game/provider/data.dart'; -import 'package:jeweled_game/utils/game_utils.dart'; -import 'package:provider/provider.dart'; -import 'package:overlay_support/overlay_support.dart'; - -class Home extends StatefulWidget { - static const String id = 'home'; - - @override - _HomeState createState() => _HomeState(); -} - -class _HomeState extends State<Home> { - @override - void initState() { - super.initState(); - - Data myProvider = Provider.of<Data>(context, listen: false); - myProvider.initParametersValues(); - myProvider.loadCurrentSavedState(); - } - - @override - Widget build(BuildContext context) { - Data myProvider = Provider.of<Data>(context); - double boardWidth = MediaQuery.of(context).size.width; - - List<Widget> menuActions = []; - - if (myProvider.isGameRunning) { - menuActions = [ - TextButton( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: Colors.blue, - width: 4, - ), - ), - child: Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - ), - onPressed: () => toast('Long press to quit game...'), - onLongPress: () => GameUtils.quitGame(myProvider), - ), - ]; - } - - return Scaffold( - appBar: AppBar( - actions: menuActions, - ), - body: SafeArea( - child: Center( - child: myProvider.isGameRunning - ? Game.buildGameWidget(myProvider, boardWidth) - : Parameters.buildParametersSelector(myProvider), - ), - ), - ); - } -} diff --git a/lib/ui/painters/game_board_painter.dart b/lib/ui/painters/game_board_painter.dart new file mode 100644 index 0000000000000000000000000000000000000000..08cad440ef153547368e0ec7ac1a1c5e01513137 --- /dev/null +++ b/lib/ui/painters/game_board_painter.dart @@ -0,0 +1,121 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:jeweled/models/game_cell.dart'; +import 'package:jeweled/models/game.dart'; +import 'package:jeweled/models/cell_location.dart'; +import 'package:jeweled/utils/color_theme.dart'; + +class GameBoardPainter extends CustomPainter { + const GameBoardPainter(this.currentGame); + + final Game currentGame; + + static const drawTextValue = false; + + @override + void paint(Canvas canvas, Size size) { + final int sizeHorizontal = currentGame.settings.boardSize; + final int sizeVertical = currentGame.settings.boardSize; + + final List cells = currentGame.board.cells; + final double cellSize = size.width / max(sizeHorizontal, sizeVertical); + + // background + for (var row = 0; row < sizeVertical; row++) { + final double y = cellSize * row; + for (var col = 0; col < sizeHorizontal; col++) { + final double x = cellSize * col; + + final GameCell cell = cells[row][col]; + final int colorCode = ColorTheme.getColorCode(cell.value); + + final cellPaintBackground = Paint(); + cellPaintBackground.color = Color(colorCode); + cellPaintBackground.style = PaintingStyle.fill; + + final Rect cellBackground = + Rect.fromPoints(Offset(x - 1, y - 1), Offset(x + cellSize + 2, y + cellSize + 2)); + + canvas.drawRect(cellBackground, cellPaintBackground); + + // draw value on cell + if (drawTextValue) { + final textPainter = TextPainter( + text: TextSpan( + text: cell.value.toString(), + style: const TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + textDirection: TextDirection.ltr, + )..layout( + minWidth: 0, + maxWidth: size.width, + ); + textPainter.paint(canvas, Offset(x + 4, y + 2)); + } + } + } + + // borders + const double borderSize = 4; + final cellPaintBorder = Paint(); + cellPaintBorder.color = ColorTheme.getBorderColor(); + cellPaintBorder.strokeWidth = borderSize; + cellPaintBorder.strokeCap = StrokeCap.round; + + for (var row = 0; row < sizeVertical; row++) { + final double y = cellSize * row; + for (var col = 0; col < sizeHorizontal; col++) { + final double x = cellSize * col; + + final GameCell cell = cells[row][col]; + final int? cellValue = cell.value; + + // top border + if ((row == 0) || + (row > 1 && + cellValue != currentGame.getCellValue(CellLocation.go(row - 1, col)))) { + final Offset borderStart = Offset(x, y); + final Offset borderStop = Offset(x + cellSize, y); + canvas.drawLine(borderStart, borderStop, cellPaintBorder); + } + + // bottom border + if ((row == sizeVertical - 1) || + ((row + 1) < sizeVertical && + cellValue != currentGame.getCellValue(CellLocation.go(row + 1, col)))) { + final Offset borderStart = Offset(x, y + cellSize); + final Offset borderStop = Offset(x + cellSize, y + cellSize); + canvas.drawLine(borderStart, borderStop, cellPaintBorder); + } + + // left border + if ((col == 0) || + (col > 1 && + cellValue != currentGame.getCellValue(CellLocation.go(row, col - 1)))) { + final Offset borderStart = Offset(x, y); + final Offset borderStop = Offset(x, y + cellSize); + canvas.drawLine(borderStart, borderStop, cellPaintBorder); + } + + // right border + if ((col == sizeHorizontal - 1) || + ((col + 1) < sizeHorizontal && + cellValue != currentGame.getCellValue(CellLocation.go(row, col + 1)))) { + final Offset borderStart = Offset(x + cellSize, y); + final Offset borderStop = Offset(x + cellSize, y + cellSize); + canvas.drawLine(borderStart, borderStop, cellPaintBorder); + } + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } +} diff --git a/lib/ui/screens/game.dart b/lib/ui/screens/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..88777784bdce9367b3871150f57a79f017e860ce --- /dev/null +++ b/lib/ui/screens/game.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:jeweled/cubit/game_cubit.dart'; +import 'package:jeweled/ui/widgets/game.dart'; +import 'package:jeweled/ui/widgets/parameters.dart'; + +class ScreenGame extends StatelessWidget { + const ScreenGame({super.key}); + + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.background, + child: Column( + children: <Widget>[ + const SizedBox(height: 8), + BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return gameState.currentGame.isRunning ? const GameWidget() : const Parameters(); + }, + ), + ], + ), + ); + } +} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart new file mode 100644 index 0000000000000000000000000000000000000000..6b24bb4c334f0dbb526ffb75ee19dfc788d81420 --- /dev/null +++ b/lib/ui/skeleton.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +import 'package:jeweled/ui/screens/game.dart'; +import 'package:jeweled/ui/widgets/global_app_bar.dart'; + +class SkeletonScreen extends StatelessWidget { + const SkeletonScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: GlobalAppBar(), + extendBodyBehindAppBar: false, + body: ScreenGame(), + backgroundColor: Theme.of(context).colorScheme.background, + ); + } +} diff --git a/lib/ui/widgets/app_titles.dart b/lib/ui/widgets/app_titles.dart new file mode 100644 index 0000000000000000000000000000000000000000..7cbbb2030419047b3dcf093a2195a498bd8e8ce9 --- /dev/null +++ b/lib/ui/widgets/app_titles.dart @@ -0,0 +1,17 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +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.headlineLarge!.apply(fontWeightDelta: 2), + ); + } +} diff --git a/lib/ui/widgets/game.dart b/lib/ui/widgets/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..3170f63f912fb6b0a8dd03ec3a62dc59fe75a5a9 --- /dev/null +++ b/lib/ui/widgets/game.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:jeweled/cubit/game_cubit.dart'; +import 'package:jeweled/models/game.dart'; +import 'package:jeweled/ui/widgets/game_board.dart'; +import 'package:jeweled/ui/widgets/game_bottom_buttons.dart'; +import 'package:jeweled/ui/widgets/game_top_indicator.dart'; + +class GameWidget extends StatelessWidget { + const GameWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 8), + const GameTopIndicatorWidget(), + const SizedBox(height: 2), + const GameBoard(), + const SizedBox(height: 2), + currentGame.isFinished ? const GameBottomButtonsWidget() : const SizedBox.shrink(), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game_board.dart b/lib/ui/widgets/game_board.dart new file mode 100644 index 0000000000000000000000000000000000000000..ab9265d89728819612587a86e890975cbc4996f0 --- /dev/null +++ b/lib/ui/widgets/game_board.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:jeweled/cubit/game_cubit.dart'; +import 'package:jeweled/models/game.dart'; +import 'package:jeweled/models/cell_location.dart'; +import 'package:jeweled/ui/painters/game_board_painter.dart'; + +class GameBoard extends StatelessWidget { + const GameBoard({super.key}); + + @override + Widget build(BuildContext context) { + final double displayWidth = MediaQuery.of(context).size.width; + + return Center( + child: BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return GestureDetector( + onTapUp: (details) { + double xTap = details.localPosition.dx; + double yTap = details.localPosition.dy; + int col = xTap ~/ (displayWidth / currentGame.settings.boardSize); + int row = yTap ~/ (displayWidth / currentGame.settings.boardSize); + + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.tapOnCell(CellLocation.go(row, col)); + }, + child: CustomPaint( + size: Size(displayWidth, displayWidth), + willChange: false, + painter: GameBoardPainter(currentGame), + isComplex: true, + ), + ); + }, + ), + ); + } +} diff --git a/lib/ui/widgets/game_bottom_buttons.dart b/lib/ui/widgets/game_bottom_buttons.dart new file mode 100644 index 0000000000000000000000000000000000000000..684279ec3a2f7cd3e33002ab86397109049021d6 --- /dev/null +++ b/lib/ui/widgets/game_bottom_buttons.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:jeweled/cubit/game_cubit.dart'; + +class GameBottomButtonsWidget extends StatelessWidget { + const GameBottomButtonsWidget({super.key}); + + @override + Widget build(BuildContext context) { + String decorationImageAssetName = 'assets/icons/game_fail.png'; + + Widget decorationWidget = TextButton( + child: Image( + image: AssetImage(decorationImageAssetName), + fit: BoxFit.fill, + ), + onPressed: () => null, + ); + + return Container( + child: Table( + defaultColumnWidth: IntrinsicColumnWidth(), + children: [ + TableRow( + children: [ + Column( + children: [decorationWidget], + ), + Column( + children: [ + TextButton( + child: Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.quitGame(); + }, + ) + ], + ), + Column( + children: [decorationWidget], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/ui/widgets/game_top_indicator.dart b/lib/ui/widgets/game_top_indicator.dart new file mode 100644 index 0000000000000000000000000000000000000000..298c502d72b5230038b4e03a81617744f27d288a --- /dev/null +++ b/lib/ui/widgets/game_top_indicator.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:jeweled/cubit/game_cubit.dart'; +import 'package:jeweled/models/game.dart'; + +class GameTopIndicatorWidget extends StatelessWidget { + const GameTopIndicatorWidget({super.key}); + + @override + Widget build(BuildContext context) { + const Color scoreTextColor = Colors.white; + const Color movesCountTextColor = Colors.grey; + const Color availableBlocksCountTextColor = Colors.green; + + const double scoreFontSize = 40; + const double movesCountFontSize = 15; + const double availableBlocksCountFontSize = 20; + + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return Table( + children: [ + TableRow( + children: [ + Column( + children: [ + Text( + currentGame.score.toString(), + style: const TextStyle( + fontSize: scoreFontSize, + fontWeight: FontWeight.w600, + color: scoreTextColor, + ), + ), + Text( + currentGame.movesCount.toString(), + style: const TextStyle( + fontSize: movesCountFontSize, + fontWeight: FontWeight.w600, + color: movesCountTextColor, + ), + ), + ], + ), + Column( + children: [ + Text( + currentGame.availableBlocksCount.toString(), + style: const TextStyle( + fontSize: availableBlocksCountFontSize, + fontWeight: FontWeight.w600, + color: availableBlocksCountTextColor, + ), + ), + ], + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..afcf1c9304ebc53b9758b5f91698f93fbdc8a517 --- /dev/null +++ b/lib/ui/widgets/global_app_bar.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:jeweled/cubit/game_cubit.dart'; +import 'package:jeweled/models/game.dart'; +import 'package:jeweled/ui/widgets/app_titles.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final List<Widget> menuActions = []; + + if (currentGame.isRunning) { + menuActions.add(TextButton( + child: Container( + child: Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + ), + onPressed: () => null, + onLongPress: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.quitGame(); + }, + )); + } + + return AppBar( + title: const AppTitle(text: 'app_name'), + actions: menuActions, + ); + }, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/widgets/parameters.dart b/lib/ui/widgets/parameters.dart new file mode 100644 index 0000000000000000000000000000000000000000..6ee9a475c856ea78eddffd6295ade21f9d255dd3 --- /dev/null +++ b/lib/ui/widgets/parameters.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:jeweled/config/default_game_settings.dart'; +import 'package:jeweled/cubit/game_cubit.dart'; +import 'package:jeweled/cubit/settings_cubit.dart'; +import 'package:jeweled/models/game_settings.dart'; + +class Parameters extends StatelessWidget { + const Parameters({super.key}); + + static const double separatorHeight = 2.0; + static const double blockMargin = 3.0; + static const double blockPadding = 2.0; + static const Color buttonBackgroundColor = Colors.white; + static const Color buttonBorderColorActive = Colors.blue; + static const Color buttonBorderColorInactive = Colors.white; + static const double buttonBorderWidth = 10.0; + static const double buttonBorderRadius = 8.0; + static const double buttonPadding = 0.0; + static const double buttonMargin = 0.0; + + @override + Widget build(BuildContext context) { + return BlocBuilder<SettingsCubit, SettingsState>( + builder: (BuildContext context, SettingsState settingsState) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + final SettingsCubit settingsCubit = BlocProvider.of<SettingsCubit>(context); + + final List<Widget> lines = []; + + DefaultGameSettings.availableParameters.forEach((code) { + final List<dynamic> availableValues = DefaultGameSettings.getAvailableValues(code); + + if (availableValues.length > 1) { + final List<Widget> parameterButtons = []; + + final dynamic currentValue = settingsCubit.getParameterValue(code); + + availableValues.forEach((value) { + final bool isActive = (value == currentValue); + final String imageAsset = code + '_' + value.toString(); + + final Widget parameterButton = TextButton( + child: Container( + margin: EdgeInsets.all(buttonMargin), + padding: EdgeInsets.all(buttonPadding), + decoration: BoxDecoration( + color: buttonBackgroundColor, + borderRadius: BorderRadius.circular(buttonBorderRadius), + border: Border.all( + color: isActive ? buttonBorderColorActive : buttonBorderColorInactive, + width: buttonBorderWidth, + ), + ), + child: buildImageWidget(imageAsset), + ), + onPressed: () => settingsCubit.setParameterValue(code, value), + ); + + parameterButtons.add(parameterButton); + }); + + lines.add(Table( + defaultColumnWidth: IntrinsicColumnWidth(), + children: [ + TableRow( + children: parameterButtons, + ), + ], + )); + + lines.add(SizedBox(height: separatorHeight)); + } + }); + + return Container( + child: Column( + children: [ + SizedBox(height: separatorHeight), + Column( + children: lines, + ), + SizedBox(height: separatorHeight), + buildStartNewGameButton(gameCubit, settingsState.settings), + ], + ), + ); + }, + ); + } + + static Image buildImageWidget(String imageAssetCode) { + return Image( + image: AssetImage('assets/icons/' + imageAssetCode + '.png'), + fit: BoxFit.fill, + ); + } + + static Container buildImageContainerWidget(String imageAssetCode) { + return Container( + child: buildImageWidget(imageAssetCode), + ); + } + + static Column buildDecorationImageWidget() { + return Column( + children: [ + TextButton( + child: buildImageContainerWidget('placeholder'), + onPressed: () => null, + ), + ], + ); + } + + static Container buildStartNewGameButton(GameCubit gameCubit, GameSettings settings) { + return Container( + margin: EdgeInsets.all(blockMargin), + padding: EdgeInsets.all(blockPadding), + child: Table( + defaultColumnWidth: IntrinsicColumnWidth(), + children: [ + TableRow( + children: [ + buildDecorationImageWidget(), + Column( + children: [ + TextButton( + child: buildImageContainerWidget('button_start'), + onPressed: () => gameCubit.startNewGame(settings), + ), + ], + ), + buildDecorationImageWidget(), + ], + ), + ], + ), + ); + } +} diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart deleted file mode 100644 index afe0c62acc2bd6722b9881925bd514b2797a73e0..0000000000000000000000000000000000000000 --- a/lib/utils/board_utils.dart +++ /dev/null @@ -1,270 +0,0 @@ -import 'dart:math'; - -import 'package:jeweled_game/entities/cell.dart'; -import 'package:jeweled_game/provider/data.dart'; - -class BoardUtils { - static printGrid(List cells) { - print(''); - print('-------'); - for (var rowIndex = 0; rowIndex < cells.length; rowIndex++) { - String row = ''; - for (var colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { - row += cells[rowIndex][colIndex].value; - } - print(row); - } - print('-------'); - print(''); - } - - static createNewBoard(Data myProvider) { - int boardSizeHorizontal = myProvider.sizeHorizontal; - int boardSizeVertical = myProvider.sizeVertical; - int maxValue = myProvider.colorsCount; - - var rand = new Random(); - - List<List<Cell>> grid = []; - for (var rowIndex = 0; rowIndex < boardSizeVertical; rowIndex++) { - List<Cell> row = []; - for (var colIndex = 0; colIndex < boardSizeHorizontal; colIndex++) { - int value = 1 + rand.nextInt(maxValue); - row.add(Cell(value.toString())); - } - grid.add(row); - } - printGrid(grid); - - myProvider.resetGame(); - myProvider.updateCells(grid); - myProvider.updateAvailableBlocksCount(getAvailableBlocks(myProvider).length); - } - - // static List createBoardFromSavedState(Data myProvider, String savedBoard) { - // List<List<Cell?>> board = []; - // int boardSize = pow((savedBoard.length / 6), 1 / 2).round(); - // String boardSizeAsString = boardSize.toString() + 'x' + boardSize.toString(); - // myProvider.updateParameterSize(boardSizeAsString); - - // int index = 0; - // for (var rowIndex = 0; rowIndex < boardSize; rowIndex++) { - // List<Cell?> row = []; - // for (var colIndex = 0; colIndex < boardSize; colIndex++) { - // String value = savedBoard[index++]; - - // Cell cell = Cell(value); - - // row.add(cell); - // } - // board.add(row); - // } - - // printGrid(board); - - // return board; - // } - - static List<List<int>> getSiblingCells( - Data myProvider, row, col, List<List<int>> siblingCells) { - int boardSizeHorizontal = myProvider.sizeHorizontal; - int boardSizeVertical = myProvider.sizeVertical; - - String referenceValue = myProvider.getCellValue(row, col); - - for (var deltaRow = -1; deltaRow <= 1; deltaRow++) { - for (var deltaCol = -1; deltaCol <= 1; deltaCol++) { - if (deltaCol == 0 || deltaRow == 0) { - int candidateRow = row + deltaRow; - int candidateCol = col + deltaCol; - - if ((candidateRow >= 0 && candidateRow < boardSizeVertical) && - (candidateCol >= 0 && candidateCol < boardSizeHorizontal)) { - if (myProvider.getCellValue(candidateRow, candidateCol) == referenceValue) { - bool alreadyFound = false; - for (var index = 0; index < siblingCells.length; index++) { - if ((siblingCells[index][0] == candidateRow) && - (siblingCells[index][1] == candidateCol)) { - alreadyFound = true; - } - } - if (!alreadyFound) { - siblingCells.add([candidateRow, candidateCol]); - siblingCells = - getSiblingCells(myProvider, candidateRow, candidateCol, siblingCells); - } - } - } - } - } - } - - return siblingCells; - } - - static List getAvailableBlocks(Data myProvider) { - int boardSizeHorizontal = myProvider.sizeHorizontal; - int boardSizeVertical = myProvider.sizeVertical; - - List blocks = []; - - for (var row = 0; row < boardSizeVertical; row++) { - for (var col = 0; col < boardSizeHorizontal; col++) { - if (myProvider.getCellValue(row, col) != '0') { - // if current cell not already in a found block - bool alreadyFound = false; - blocks.forEach((foundBlock) { - foundBlock.forEach((foundBlockCell) { - if ((foundBlockCell[0] == row) && (foundBlockCell[1] == col)) { - alreadyFound = true; - } - }); - }); - if (!alreadyFound) { - List<List<int>> block = getSiblingCells(myProvider, row, col, []); - if (block.length >= 3) { - blocks.add(block); - } - } - } - } - } - - return blocks; - } - - static bool checkBoardIsBlocked(Data myProvider) { - int boardSizeHorizontal = myProvider.sizeHorizontal; - int boardSizeVertical = myProvider.sizeVertical; - - for (var row = 0; row < boardSizeVertical; row++) { - for (var col = 0; col < boardSizeHorizontal; col++) { - if (myProvider.getCellValue(row, col) != '0') { - List<List<int>> block = getSiblingCells(myProvider, row, col, []); - if (block.length >= 3) { - // found one block => ok, not locked - return false; - } - } - } - } - - print('Board is locked'); - return true; - } - - static bool isInBoard(Data myProvider, int row, int col) { - if (row > 0 && - row < myProvider.sizeHorizontal && - col > 0 && - col < myProvider.sizeVertical) { - return true; - } - return false; - } - - static String getFillValue(Data myProvider, int row, int col) { - // build a list of values to pick one - List<String> values = []; - - // All eligible values - int maxValue = myProvider.colorsCount; - for (int i = 1; i <= maxValue; i++) { - values.add(i.toString()); - } - - // Add values of current col - for (int r = 0; r <= myProvider.sizeVertical; r++) { - if (isInBoard(myProvider, r, col)) { - String value = myProvider.getCellValue(r, col); - if (value != '0') { - values.add(value); - } - } - } - - // Add values of sibling cols - for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { - int c = col + deltaCol; - for (int r = 0; r < myProvider.sizeVertical; r++) { - if (isInBoard(myProvider, r, c)) { - String value = myProvider.getCellValue(r, c); - if (value != '0') { - values.add(value); - } - } - } - } - - // Add values of sibling cells - for (int deltaCol = -2; deltaCol <= 2; deltaCol++) { - int c = col + deltaCol; - for (int deltaRow = -2; deltaRow <= 2; deltaRow++) { - int r = row + deltaRow; - if (isInBoard(myProvider, r, c)) { - String value = myProvider.getCellValue(r, c); - if (value != '0') { - values.add(value); - } - } - } - } - - // Pick random value from "ponderated" list - return values[Random().nextInt(values.length)]; - } - - static moveCellsDown(Data myProvider) { - int boardSizeHorizontal = myProvider.sizeHorizontal; - int boardSizeVertical = myProvider.sizeVertical; - - for (int row = 0; row < boardSizeVertical; row++) { - for (int col = 0; col < boardSizeHorizontal; col++) { - if (myProvider.getCellValue(row, col) == '0') { - for (int r = row; r > 0; r--) { - myProvider.updateCellValue(col, r, myProvider.getCellValue(r - 1, col)); - } - myProvider.updateCellValue(col, 0, getFillValue(myProvider, row, col)); - } - } - } - } - - static deleteCell(Data myProvider, int row, int col) { - myProvider.updateCellValue(col, row, '0'); - } - - static void deleteBlock(Data myProvider, List<List<int>> block) { - // Sort cells from top to bottom - block.sort((cell1, cell2) => cell1[0].compareTo(cell2[0])); - // Delete all cells - block.forEach((element) { - deleteCell(myProvider, element[0], element[1]); - }); - // Gravity! - moveCellsDown(myProvider); - } - - static int getScoreFromBlock(int blockSize) { - return 3 * (blockSize - 2); - } - - static void tapOnCell(Data myProvider, int row, int col) { - String cellValue = myProvider.getCellValue(row, col); - print('Tap on cell[' + col.toString() + '][' + row.toString() + ']: ' + cellValue); - - if (cellValue != '0') { - List<List<int>> block = getSiblingCells(myProvider, row, col, []); - if (block.length >= 3) { - deleteBlock(myProvider, block); - myProvider.increaseMovesCount(); - myProvider.increaseScore(block.length); - myProvider.updateAvailableBlocksCount(getAvailableBlocks(myProvider).length); - } - } - - if (checkBoardIsBlocked(myProvider)) { - myProvider.updateGameIsFinished(true); - } - } -} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97 --- /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/lib/utils/color_theme.dart b/lib/utils/color_theme.dart new file mode 100644 index 0000000000000000000000000000000000000000..2b10565a36d1f956202b2d30f13fb21bef8bd707 --- /dev/null +++ b/lib/utils/color_theme.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class ColorTheme { + static Map<String, List<int>> itemColors = { + 'default': [ + 0xffffff, + 0xe63a3f, + 0x708cfd, + 0x359c35, + 0xffce2c, + 0xff6f43, + 0xa13cb1, + 0x38ffff, + 0xf2739d, + ], + }; + static int defaultItemColor = 0x808080; + + static int getColorCode(int? value) { + const skin = 'default'; + + if (value != null && itemColors.containsKey(skin) && null != itemColors[skin]) { + List<int> skinColors = itemColors[skin] ?? []; + if (skinColors.length > value) { + return (skinColors[value]) | 0xFF000000; + } + } + return defaultItemColor | 0xFF000000; + } + + static Color getBorderColor() { + return Colors.black; + } +} diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart deleted file mode 100644 index 3515d2fad48b19c56491eb47bcf233ce76174fe0..0000000000000000000000000000000000000000 --- a/lib/utils/game_utils.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:jeweled_game/provider/data.dart'; -import 'package:jeweled_game/utils/board_utils.dart'; -// import 'package:jeweled_game/utils/board_utils.dart'; - -class GameUtils { - static Future<void> quitGame(Data myProvider) async { - myProvider.updateGameIsRunning(false); - } - - static Future<void> startNewGame(Data myProvider) async { - print('Starting game'); - print('- level: ' + myProvider.parameterLevel); - print('- size: ' + myProvider.parameterSize); - - myProvider.resetGame(); - BoardUtils.createNewBoard(myProvider); - - myProvider.updateGameIsRunning(true); - } - - static void deleteSavedGame(Data myProvider) { - myProvider.resetCurrentSavedState(); - } - - static void resumeSavedGame(Data myProvider) { - Map<String, dynamic> savedState = myProvider.getCurrentSavedState(); - if (savedState.isNotEmpty) { - try { - myProvider.setParameterValue('level', savedState['level']); - myProvider.setParameterValue('size', savedState['size']); - myProvider.setParameterValue('skin', savedState['skin']); - - // myProvider.updateCells( - // BoardUtils.createBoardFromSavedState(myProvider, savedState['board'])); - myProvider.updateGameIsRunning(true); - } catch (e) { - print('Failed to resume game. Will start new one instead.'); - myProvider.resetCurrentSavedState(); - myProvider.initParametersValues(); - startNewGame(myProvider); - } - } else { - myProvider.resetCurrentSavedState(); - myProvider.initParametersValues(); - startNewGame(myProvider); - } - } -} diff --git a/pubspec.lock b/pubspec.lock index f78ae3bfbc5b91625f56452dcbc944e3e78db356..f0a711fa116acf1950fd1e05861e1a227e16cc00 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,14 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - async: + args: dependency: transitive description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.4.2" + bloc: + dependency: transitive + description: + name: bloc + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" + url: "https://pub.dev" + source: hosted + version: "8.1.2" characters: dependency: transitive description: @@ -17,14 +25,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" collection: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + sha256: de63e3b422adfc97f256cbb3f8cf12739b6a4993d390f3cadb3f51837afaefe5 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + easy_logger: + dependency: transitive + description: + name: easy_logger + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" + source: hosted + version: "0.0.2" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" ffi: dependency: transitive description: @@ -37,20 +85,57 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae + url: "https://pub.dev" + source: hosted + version: "8.1.3" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_web_plugins: dependency: transitive description: flutter source: sdk version: "0.0.0" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hydrated_bloc: + dependency: "direct main" + description: + name: hydrated_bloc + sha256: c925e49704c052a8f249226ae7603f86bfa776b910816390763b956c71d2cbaf + url: "https://pub.dev" + source: hosted + version: "9.1.3" + intl: + dependency: transitive + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" material_color_utilities: dependency: transitive description: @@ -63,10 +148,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" nested: dependency: transitive description: @@ -75,14 +160,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - overlay_support: - dependency: "direct main" - description: - name: overlay_support - sha256: fc39389bfd94e6985e1e13b2a88a125fc4027608485d2d4e2847afe1b2bb339c - url: "https://pub.dev" - source: hosted - version: "2.1.0" path: dependency: transitive description: @@ -91,6 +168,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -103,10 +204,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -119,34 +220,34 @@ packages: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" url: "https://pub.dev" source: hosted - version: "6.0.5" + version: "6.1.1" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences - sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_android: dependency: transitive description: @@ -159,47 +260,71 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + unicons: + dependency: "direct main" + description: + name: unicons + sha256: dbfcf93ff4d4ea19b324113857e358e4882115ab85db04417a4ba1c72b17a670 + url: "https://pub.dev" + source: hosted + version: "2.1.1" vector_math: dependency: transitive description: @@ -212,26 +337,26 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" win32: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.2.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 54385493e1b3a33abebd08a74d11c99fc6c6cdea..7f45afaa50d390a418c62fb833059908491b3c9a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,9 @@ -name: jeweled_game +name: jeweled description: Jeweled Game + publish_to: 'none' -version: 1.0.0+1 + +version: 0.0.9+9 environment: sdk: '^3.0.0' @@ -9,11 +11,27 @@ environment: dependencies: flutter: sdk: flutter - provider: ^6.0.5 - shared_preferences: ^2.2.1 - overlay_support: ^2.1.0 + easy_localization: ^3.0.1 + equatable: ^2.0.5 + flutter_bloc: ^8.1.1 + hydrated_bloc: ^9.0.0 + path_provider: ^2.0.11 + unicons: ^2.1.1 flutter: - uses-material-design: true + uses-material-design: false assets: - assets/icons/ + - assets/translations/ + + fonts: + - family: Nunito + fonts: + - asset: assets/fonts/Nunito-Bold.ttf + weight: 700 + - asset: assets/fonts/Nunito-Medium.ttf + weight: 500 + - asset: assets/fonts/Nunito-Regular.ttf + weight: 400 + - asset: assets/fonts/Nunito-Light.ttf + weight: 300