diff --git a/android/app/build.gradle b/android/app/build.gradle index 9e2643e56e910f0a8ff4a8b88a7d0968232c0bf3..c3c8f6672ba313bb98c44122715d166f56e0d829 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -37,7 +37,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 namespace "org.benoitharrault.solitaire" defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index cd2d833ca96b3d1ada4a39df51dc5f5ee67665b7..30298b3b3f04073678e48519b8c043edba635df8 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.17 -app.versionCode=17 +app.versionName=0.0.18 +app.versionCode=18 diff --git a/assets/icons/button_delete_saved_game.png b/assets/icons/button_delete_saved_game.png deleted file mode 100644 index 5e4f217689b11e444b7163557d7e5d68f3bbfe7d..0000000000000000000000000000000000000000 Binary files a/assets/icons/button_delete_saved_game.png and /dev/null differ diff --git a/assets/icons/button_resume_game.png b/assets/icons/button_resume_game.png deleted file mode 100644 index b2ea0a02d05e42377eb551a4b51428b511a32f5d..0000000000000000000000000000000000000000 Binary files a/assets/icons/button_resume_game.png and /dev/null differ diff --git a/assets/icons/game_fail.png b/assets/icons/game_fail.png deleted file mode 100644 index 93f2801f9d6bb2ce508e1293cd64d6ff2e9970ec..0000000000000000000000000000000000000000 Binary files a/assets/icons/game_fail.png and /dev/null differ diff --git a/assets/icons/layout_diamond.png b/assets/icons/layout_diamond.png deleted file mode 100644 index d20c4ea546b070361e0d0d3aeb6682bca69273a6..0000000000000000000000000000000000000000 Binary files a/assets/icons/layout_diamond.png and /dev/null differ diff --git a/assets/icons/layout_english.png b/assets/icons/layout_english.png deleted file mode 100644 index 3ca9c27286c0fc3ec7d3808cd23118965b95892d..0000000000000000000000000000000000000000 Binary files a/assets/icons/layout_english.png and /dev/null differ diff --git a/assets/icons/layout_french.png b/assets/icons/layout_french.png deleted file mode 100644 index 71837c08f529075e14fce4db01f166cf9ac36d6c..0000000000000000000000000000000000000000 Binary files a/assets/icons/layout_french.png and /dev/null differ diff --git a/assets/icons/layout_german.png b/assets/icons/layout_german.png deleted file mode 100644 index 37c51ca8fdb173b33d06b23435a806fdb215b9bd..0000000000000000000000000000000000000000 Binary files a/assets/icons/layout_german.png and /dev/null differ diff --git a/assets/icons/skin_default.png b/assets/icons/skin_default.png deleted file mode 100644 index fbe9cb151a27e54b848f7cd48e397cd308f51dad..0000000000000000000000000000000000000000 Binary files a/assets/icons/skin_default.png and /dev/null differ diff --git a/assets/translations/en.json b/assets/translations/en.json index edfb95a30bace7bf5b2c37e3946e068ccbea735e..13da5fcbf5903240f481642c2ef432873c7a3d7c 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,8 +1,6 @@ { "app_name": "Solitaire", - "long_press_to_quit": "Long press to quit game...", - "bottom_nav_home": "Game", "bottom_nav_settings": "Settings", "bottom_nav_about": "About", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index a4e231eb9e29deab5b2082bfcd0e981020be4b9e..0d43876a4b6ebfadf9222ed4ac88d5c53d5064ef 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,8 +1,6 @@ { "app_name": "Solitaire", - "long_press_to_quit": "Appuyer longtemps pour quitter le jeu...", - "bottom_nav_home": "Jeu", "bottom_nav_settings": "Réglages", "bottom_nav_about": "Infos", diff --git a/fastlane/metadata/android/en-US/changelogs/18.txt b/fastlane/metadata/android/en-US/changelogs/18.txt new file mode 100644 index 0000000000000000000000000000000000000000..9bb4c4dee10043312bc54a49a258422743bdc041 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/18.txt @@ -0,0 +1 @@ +Improve application architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/18.txt b/fastlane/metadata/android/fr-FR/changelogs/18.txt new file mode 100644 index 0000000000000000000000000000000000000000..5face79935538bfb3371042404b6c3586cc87f31 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/18.txt @@ -0,0 +1 @@ +Amélioration de l'architecture de l'application. diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh index 4a9ca31a5b6aa7eee2cde162eecb09d0c2b0ce81..2191261bef769712d228d7a08e5dc97a727ee484 100755 --- a/icons/build_game_icons.sh +++ b/icons/build_game_icons.sh @@ -18,18 +18,10 @@ ICON_SIZE=192 AVAILABLE_GAME_IMAGES=" button_back button_start - button_resume_game - button_delete_saved_game - game_fail game_win placeholder " -# Settings images -AVAILABLES_GAME_SETTINGS=" - layout:french,german,english,diamond -" - # Skins AVAILABLE_SKINS=" default @@ -84,25 +76,9 @@ function build_icon() { optipng ${OPTIPNG_OPTIONS} ${TARGET} } -function build_settings_icons() { - INPUT_STRING="$1" - - SETTING_NAME="$(echo "${INPUT_STRING}" | cut -d":" -f1)" - SETTING_VALUES="$(echo "${INPUT_STRING}" | cut -d":" -f2 | tr "," " ")" - - for SETTING_VALUE in ${SETTING_VALUES} - do - SETTING_CODE="${SETTING_NAME}_${SETTING_VALUE}" - build_icon ${CURRENT_DIR}/${SETTING_CODE}.svg ${ASSETS_DIR}/icons/${SETTING_CODE}.png - done -} - function build_icon_for_skin() { SKIN_CODE="$1" - # skin main image - build_icon ${CURRENT_DIR}/skin_${SKIN_CODE}.svg ${ASSETS_DIR}/icons/skin_${SKIN_CODE}.png - # skin images for SKIN_IMAGE in ${SKIN_IMAGES} do @@ -126,12 +102,6 @@ do build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png done -# build settings images -for GAME_SETTING in ${AVAILABLES_GAME_SETTINGS} -do - build_settings_icons "${GAME_SETTING}" -done - # build skins images for SKIN in ${AVAILABLE_SKINS} do diff --git a/icons/button_delete_saved_game.svg b/icons/button_delete_saved_game.svg deleted file mode 100644 index ac7eefef476f761903fe781b8c86d0c94323550a..0000000000000000000000000000000000000000 --- a/icons/button_delete_saved_game.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 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#ee7d49" stroke="#fff" stroke-width=".238"/><path d="m61.07 35.601-1.7399 27.837c-0.13442 2.1535-1.9205 3.8312-4.0781 3.8312h-16.84c-2.1576 0-3.9437-1.6777-4.0781-3.8312l-1.7399-27.837h-2.6176c-0.84621 0-1.5323-0.68613-1.5323-1.5323 0-0.84655 0.68613-1.5323 1.5323-1.5323h33.711c0.84621 0 1.5323 0.68578 1.5323 1.5323 0 0.84621-0.68613 1.5323-1.5323 1.5323zm-3.2617 0h-21.953l1.4715 26.674c0.05985 1.0829 0.95531 1.9305 2.0403 1.9305h14.929c1.085 0 1.9804-0.84757 2.0403-1.9305zm-10.977 3.0647c0.78977 0 1.4301 0.6403 1.4301 1.4301v19.614c0 0.78977-0.6403 1.4301-1.4301 1.4301s-1.4301-0.6403-1.4301-1.4301v-19.614c0-0.78977 0.6403-1.4301 1.4301-1.4301zm-6.1293 0c0.80004 0 1.4588 0.62935 1.495 1.4286l0.89647 19.719c0.03182 0.70016-0.50998 1.2933-1.2101 1.3255-0.01915 7.02e-4 -0.03831 1e-3 -0.05781 1e-3 -0.74462 0-1.3596-0.58215-1.4003-1.3261l-1.0757-19.719c-0.0407-0.74701 0.53188-1.3852 1.2786-1.4259 0.02462-0.0014 0.04926-2e-3 0.07388-2e-3zm12.259 0c0.74804 0 1.3541 0.60609 1.3541 1.3541 0 0.02462-3.28e-4 0.04926-0.0017 0.07388l-1.0703 19.618c-0.04379 0.80106-0.70597 1.4281-1.5081 1.4281-0.74804 0-1.3541-0.60609-1.3541-1.3541 0-0.02462 3.49e-4 -0.04925 0.0017-0.07388l1.0703-19.618c0.04379-0.80106 0.70597-1.4281 1.5081-1.4281zm-10.216-12.259h8.1728c2.2567 0 4.086 1.8293 4.086 4.086v2.0433h-16.344v-2.0433c0-2.2567 1.8293-4.086 4.086-4.086zm0.20453 3.0647c-0.67725 0-1.2259 0.54863-1.2259 1.2259v1.8388h10.215v-1.8388c0-0.67725-0.54863-1.2259-1.2259-1.2259z" fill="#fff" fill-rule="evenodd" stroke="#bd4812" stroke-width=".75383"/></svg> diff --git a/icons/button_resume_game.svg b/icons/button_resume_game.svg deleted file mode 100644 index 6ad8b64202d0e70f898c16c520e756fe8a934add..0000000000000000000000000000000000000000 --- a/icons/button_resume_game.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 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m39.211 31.236c-0.84086-0.84489-2.9911-0.84489-2.9911 0v34.329c0 0.84594 2.1554 0.84594 2.9993 0l28.178-15.637c0.84392-0.84086 0.85812-2.2091 0.01623-3.053z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="6.1726"/><path d="m40.355 33.714c-0.71948-0.72294-2.5594-0.72294-2.5594 0v29.373c0 0.72383 1.8442 0.72383 2.5663 0l24.11-13.38c0.7221-0.71948 0.73426-1.8902 0.01389-2.6124z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.225"/><path d="m28.369 66.919v-37.591" fill="#105ca2" stroke="#105ca2" stroke-linecap="round" stroke-width="4.0337"/></svg> diff --git a/icons/game_fail.svg b/icons/game_fail.svg deleted file mode 100644 index 2922fd7adc2bd2e813836c728f095376c73d4143..0000000000000000000000000000000000000000 --- a/icons/game_fail.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 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#d11717" stroke="#fff" stroke-width=".238"/><path d="m71.624 59.304c3.5089 3.5089 3.5089 9.0561 0 12.565-1.6976 1.6976-3.9623 2.6034-6.2261 2.6034s-4.5275-0.90569-6.2261-2.6034l-12.452-12.452-12.452 12.452c-1.6976 1.6976-3.9623 2.6034-6.2261 2.6034s-4.5275-0.90569-6.2261-2.6034c-3.5089-3.5089-3.5089-9.0561 0-12.565l12.452-12.452-12.452-12.452c-3.5089-3.5089-3.5089-9.0561 0-12.565s9.0561-3.5089 12.565 0l12.452 12.452 12.452-12.452c3.5089-3.5089 9.0561-3.5089 12.565 0s3.5089 9.0561 0 12.565l-12.452 12.452z" fill="#e7e7e7" stroke-width=".20213"/></svg> diff --git a/icons/layout_diamond.svg b/icons/layout_diamond.svg deleted file mode 100644 index 31d08b4ff97e2030eabc1e993e33647cda998c63..0000000000000000000000000000000000000000 --- a/icons/layout_diamond.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="#6ade70" stroke="#000" stroke-width="2"/><g transform="matrix(.8379 0 0 .8379 -396.44 -4.3017)"><g transform="translate(180 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(228 96)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(228 3.624e-5)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(228 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(228 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 60)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 60)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 60)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(192 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(192 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(192 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(204 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(204 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(204 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><rect x="528" y="60" width="12" height="12" fill="#babdb6" stroke="#000"/><g transform="translate(240 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g></g></svg> diff --git a/icons/layout_english.svg b/icons/layout_english.svg deleted file mode 100644 index c819ca663895daf475c3f9b5a16857ed11ead466..0000000000000000000000000000000000000000 --- a/icons/layout_english.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="#6ade70" stroke="#000" stroke-width="2"/><g transform="translate(-363 -3)"><g transform="translate(120 48)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 48)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 48)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(72 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(72 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(72 24)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(84 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(84 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(84 24)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 36)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 24)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><rect x="408" y="48" width="12" height="12" fill="#babdb6" stroke="#000"/><g transform="translate(120 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(86.461 -21.272)"><g transform="translate(33.539 21.272)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g></g></g></svg> diff --git a/icons/layout_french.svg b/icons/layout_french.svg deleted file mode 100644 index 1ff5efe0fe2c5bd4dccca0ab48ff15e650ca23d3..0000000000000000000000000000000000000000 --- a/icons/layout_french.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="#6ade70" stroke="#000" stroke-width="2"/><g transform="translate(-3 -15)"><g transform="translate(-204 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-252 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-252 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 60)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 60)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 60)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-288 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-288 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-288 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-276 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-276 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-276 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 48)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 48)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 48)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 48)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="288" y="24" width="12" height="12" fill="#babdb6" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><rect x="48" y="48" width="12" height="12" fill="#fff" stroke="#000"/><rect x="48" y="60" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 -100.3 35.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/><g transform="translate(-240 12)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g></g></svg> diff --git a/icons/layout_german.svg b/icons/layout_german.svg deleted file mode 100644 index e8266691daa33fd1a19d35cbab1ff6d6993dfd48..0000000000000000000000000000000000000000 --- a/icons/layout_german.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="#6ade70" stroke="#000" stroke-width="2"/><g transform="matrix(.84066 0 0 .84066 -95.275 -4.4834)"><g transform="translate(-168 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-168 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-168 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><rect x="168" y="60" width="12" height="12" fill="#babdb6" stroke="#000"/><g transform="translate(-120 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g></g></svg> diff --git a/icons/skin_default.svg b/icons/skin_default.svg deleted file mode 100644 index f7344d57776c1eb3ca5699290db81c999f83cc0a..0000000000000000000000000000000000000000 --- a/icons/skin_default.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="#be6ade" stroke="#000" stroke-width="2"/></svg> diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..6ac27dcf613094e37f638ebe8d69573e4219deca --- /dev/null +++ b/lib/config/default_game_settings.dart @@ -0,0 +1,33 @@ +import 'package:solitaire/utils/tools.dart'; + +class DefaultGameSettings { + // available game parameters codes + static const String parameterCodeLayout = 'layout'; + static const List<String> availableParameters = [ + parameterCodeLayout, + ]; + + // layout: available values + static const String layoutValueFrench = 'french'; + static const String layoutValueGerman = 'german'; + static const String layoutValueEnglish = 'english'; + static const String layoutValueDiamond = 'diamond'; + static const List<String> allowedLayoutValues = [ + layoutValueFrench, + layoutValueGerman, + layoutValueEnglish, + layoutValueDiamond, + ]; + // layout: default value + static const String defaultLayoutValue = layoutValueEnglish; + + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeLayout: + return DefaultGameSettings.allowedLayoutValues; + } + + printlog('Did not find any available value for game parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..622a8b66d293815ec6ce9d50f7cefd9701606f1d --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,27 @@ +import 'package:solitaire/utils/tools.dart'; + +class DefaultGlobalSettings { + // available global parameters codes + static const String parameterCodeSkin = 'skin'; + static const List<String> availableParameters = [ + parameterCodeSkin, + ]; + + // skin: available values + static const String skinValueDefault = 'default'; + static const List<String> allowedSkinValues = [ + skinValueDefault, + ]; + // skin: default value + static const String defaultSkinValue = skinValueDefault; + + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeSkin: + return DefaultGlobalSettings.allowedSkinValues; + } + + printlog('Did not find any available value for global parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/menu.dart b/lib/config/menu.dart index ca5d227fee343cb63c211fd9904fd9d1a48477b1..3d2fcfacf3e2ca175870530e2f4e33f74264bb79 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -1,10 +1,9 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:unicons/unicons.dart'; -import 'package:solitaire/ui/screens/about_page.dart'; -import 'package:solitaire/ui/screens/game_page.dart'; -import 'package:solitaire/ui/screens/settings_page.dart'; +import 'package:solitaire/ui/screens/page_about.dart'; +import 'package:solitaire/ui/screens/page_game.dart'; +import 'package:solitaire/ui/screens/page_settings.dart'; class MenuItem { final String code; @@ -19,35 +18,39 @@ class MenuItem { } class Menu { - static List<MenuItem> items = [ - const MenuItem( - code: 'bottom_nav_home', - icon: Icon(UniconsLine.home), - page: GamePage(), - ), - const MenuItem( - code: 'bottom_nav_settings', - icon: Icon(UniconsLine.setting), - page: SettingsPage(), - ), - const MenuItem( - code: 'bottom_nav_about', - icon: Icon(UniconsLine.info_circle), - page: AboutPage(), - ), - ]; + static const indexGame = 0; + static const menuItemGame = MenuItem( + code: 'bottom_nav_game', + icon: Icon(UniconsLine.home), + page: PageGame(), + ); - static Widget getPageWidget(int pageIndex) { - return Menu.items.elementAt(pageIndex).page; + static const indexSettings = 1; + static const menuItemSettings = MenuItem( + code: 'bottom_nav_settings', + icon: Icon(UniconsLine.setting), + page: PageSettings(), + ); + + static const indexAbout = 2; + static const menuItemAbout = MenuItem( + code: 'bottom_nav_about', + icon: Icon(UniconsLine.info_circle), + page: PageAbout(), + ); + + static Map<int, MenuItem> items = { + indexGame: menuItemGame, + indexSettings: menuItemSettings, + indexAbout: menuItemAbout, + }; + + static bool isIndexAllowed(int pageIndex) { + return items.keys.contains(pageIndex); } - static List<BottomNavigationBarItem> getMenuItems() { - return Menu.items - .map((MenuItem item) => BottomNavigationBarItem( - icon: item.icon, - label: tr(item.code), - )) - .toList(); + static Widget getPageWidget(int pageIndex) { + return items[pageIndex]?.page ?? menuItemGame.page; } static int itemsCount = Menu.items.length; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..aba2472b6c5e923a383e4da46abd8a1611a43e88 --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,127 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:solitaire/models/cell_location.dart'; +import 'package:solitaire/models/game.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/models/settings_global.dart'; +import 'package:solitaire/utils/tools.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( + gameSettings: state.currentGame.gameSettings, + globalSettings: state.currentGame.globalSettings, + board: state.currentGame.board, + isRunning: state.currentGame.isRunning, + isFinished: state.currentGame.isFinished, + movesCount: state.currentGame.movesCount, + remainingPegsCount: state.currentGame.remainingPegsCount, + allowedMovesCount: state.currentGame.allowedMovesCount, + ); + // game.dump(); + + updateState(game); + } + + void startNewGame({ + required GameSettings gameSettings, + required GlobalSettings globalSettings, + }) { + Game newGame = Game.createNew( + gameSettings: gameSettings, + globalSettings: globalSettings, + ); + + newGame.dump(); + + updateState(newGame); + + updateRemainingPegsCount(newGame.countRemainingPegs()); + updateAllowedMovesCount(newGame.countAllowedMoves()); + + refresh(); + } + + void quitGame() { + state.currentGame.isRunning = false; + refresh(); + } + + void updatePegValue(CellLocation location, bool hasPeg) { + state.currentGame.board.cells[location.row][location.col].hasPeg = hasPeg; + refresh(); + } + + void incrementMovesCount() { + state.currentGame.movesCount++; + refresh(); + } + + void updateRemainingPegsCount(int count) { + state.currentGame.remainingPegsCount = count; + refresh(); + } + + void updateAllowedMovesCount(int count) { + state.currentGame.allowedMovesCount = count; + if (count == 0) { + state.currentGame.isFinished = true; + } + + refresh(); + } + + void move({ + required Game currentGame, + required List<int> source, + required List<int> target, + }) { + printlog('Move from $source to $target'); + final int sourceCol = source[0]; + final int sourceRow = source[1]; + final int targetCol = target[0]; + final int targetRow = target[1]; + + final int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round(); + final int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round(); + + updatePegValue(CellLocation.go(sourceRow, sourceCol), false); + updatePegValue(CellLocation.go(targetRow, targetCol), true); + updatePegValue(CellLocation.go(middleRow, middleCol), false); + + incrementMovesCount(); + updateRemainingPegsCount(currentGame.countRemainingPegs()); + updateAllowedMovesCount(currentGame.countAllowedMoves()); + } + + @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/bottom_nav_cubit.dart b/lib/cubit/nav_cubit.dart similarity index 51% rename from lib/cubit/bottom_nav_cubit.dart rename to lib/cubit/nav_cubit.dart index 8f04dffa9682eb52139e44fcb2ab9a4d75a613a8..9d4541c6273c625de7e6b7eadf4efd170223879f 100644 --- a/lib/cubit/bottom_nav_cubit.dart +++ b/lib/cubit/nav_cubit.dart @@ -2,26 +2,32 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:solitaire/config/menu.dart'; -class BottomNavCubit extends HydratedCubit<int> { - BottomNavCubit() : super(0); +class NavCubit extends HydratedCubit<int> { + NavCubit() : super(0); void updateIndex(int index) { - if (isIndexAllowed(index)) { + if (Menu.isIndexAllowed(index)) { emit(index); } else { - goToHomePage(); + goToGamePage(); } } - bool isIndexAllowed(int index) { - return (index >= 0) && (index < Menu.itemsCount); + void goToGamePage() { + emit(Menu.indexGame); } - void goToHomePage() => emit(0); + void goToSettingsPage() { + emit(Menu.indexSettings); + } + + void goToAboutPage() { + emit(Menu.indexAbout); + } @override int fromJson(Map<String, dynamic> json) { - return 0; + return Menu.indexGame; } @override diff --git a/lib/cubit/settings_game_cubit.dart b/lib/cubit/settings_game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..7399314684c139542eb3559b67e6dceeb9641bde --- /dev/null +++ b/lib/cubit/settings_game_cubit.dart @@ -0,0 +1,64 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:solitaire/config/default_game_settings.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/utils/tools.dart'; + +part 'settings_game_state.dart'; + +class GameSettingsCubit extends HydratedCubit<GameSettingsState> { + GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault())); + + void setValues({ + String? layout, + }) { + emit( + GameSettingsState( + settings: GameSettings( + layout: layout ?? state.settings.layout, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGameSettings.parameterCodeLayout: + return GameSettings.getLayoutValueFromUnsafe(state.settings.layout); + } + return ''; + } + + void setParameterValue(String code, String value) { + printlog('GameSettingsCubit.setParameterValue'); + printlog('code: $code / value: $value'); + + String layout = code == DefaultGameSettings.parameterCodeLayout + ? value + : getParameterValue(DefaultGameSettings.parameterCodeLayout); + + setValues( + layout: layout, + ); + } + + @override + GameSettingsState? fromJson(Map<String, dynamic> json) { + String layout = json[DefaultGameSettings.parameterCodeLayout] as String; + + return GameSettingsState( + settings: GameSettings( + layout: layout, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GameSettingsState state) { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeLayout: state.settings.layout, + }; + } +} diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..b773dc69be12673b158e880e2d7e6e7bec465506 --- /dev/null +++ b/lib/cubit/settings_game_state.dart @@ -0,0 +1,19 @@ +part of 'settings_game_cubit.dart'; + +@immutable +class GameSettingsState extends Equatable { + const GameSettingsState({ + 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/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..274c113cff2bfa94e9e3ea492e9359ca11d149ca --- /dev/null +++ b/lib/cubit/settings_global_cubit.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:solitaire/config/default_global_settings.dart'; +import 'package:solitaire/models/settings_global.dart'; + +part 'settings_global_state.dart'; + +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault())); + + void setValues({ + String? skin, + }) { + emit( + GlobalSettingsState( + settings: GlobalSettings( + skin: skin ?? state.settings.skin, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGlobalSettings.parameterCodeSkin: + return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String skin = (code == DefaultGlobalSettings.parameterCodeSkin) + ? value + : getParameterValue(DefaultGlobalSettings.parameterCodeSkin); + + setValues( + skin: skin, + ); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String; + + return GlobalSettingsState( + settings: GlobalSettings( + skin: skin, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GlobalSettingsState state) { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: state.settings.skin, + }; + } +} diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e4fbdf707b4e805f2092d0ca6a68a2de1c957c6 --- /dev/null +++ b/lib/cubit/settings_global_state.dart @@ -0,0 +1,19 @@ +part of 'settings_global_cubit.dart'; + +@immutable +class GlobalSettingsState extends Equatable { + const GlobalSettingsState({ + required this.settings, + }); + + final GlobalSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/data/game_data.dart b/lib/data/game_data.dart new file mode 100644 index 0000000000000000000000000000000000000000..711256b874d00c3facc3eb99eb79a80d78bba2bc --- /dev/null +++ b/lib/data/game_data.dart @@ -0,0 +1,44 @@ +class GameData { + static const Map<String, List<String>> templates = { + 'french': [ + ' ooo ', + ' ooooo ', + 'ooo·ooo', + 'ooooooo', + 'ooooooo', + ' ooooo ', + ' ooo ', + ], + 'german': [ + ' ooo ', + ' ooo ', + ' ooo ', + 'ooooooooo', + 'oooo·oooo', + 'ooooooooo', + ' ooo ', + ' ooo ', + ' ooo ', + ], + 'english': [ + ' ooo ', + ' ooo ', + 'ooooooo', + 'ooo·ooo', + 'ooooooo', + ' ooo ', + ' ooo ', + ], + 'diamond': [ + ' o ', + ' ooo ', + ' ooooo ', + ' ooooooo ', + 'oooo·oooo', + ' ooooooo ', + ' ooooo ', + ' ooo ', + ' o ', + ] + }; +} diff --git a/lib/entities/tile.dart b/lib/entities/tile.dart deleted file mode 100644 index 5f88b97058b643f3b357bd60efdfc7bca2fe0e26..0000000000000000000000000000000000000000 --- a/lib/entities/tile.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class Tile { - int currentRow; - int currentCol; - bool hasPeg; - - Tile( - this.currentRow, - this.currentCol, - this.hasPeg, - ); - - Widget render(Data myProvider) { - List<Widget> stack = [ - hole(myProvider), - ]; - - if (hasPeg) { - stack.add(draggable(myProvider)); - } - - return Stack( - alignment: Alignment.center, - children: stack, - ); - } - - Widget hole(Data myProvider) { - String image = 'assets/skins/${myProvider.parameterSkin}_hole.png'; - - return DragTarget<List<int>>( - builder: ( - BuildContext context, - List<dynamic> accepted, - List<dynamic> rejected, - ) { - return Image( - image: AssetImage(image), - width: myProvider.tileSize, - height: myProvider.tileSize, - fit: BoxFit.fill, - ); - }, - onAcceptWithDetails: (DragTargetDetails<List<int>> source) { - List<int> target = [currentCol, currentRow]; - // printlog('(drag) Pick from ' + source.toString() + ' and drop on ' + target.toString()); - if (GameUtils.isMoveAllowed(myProvider, source.data, target)) { - GameUtils.move(myProvider, source.data, target); - } - }, - ); - } - - Widget draggable(Data myProvider) { - return Draggable<List<int>>( - data: [currentCol, currentRow], - - // Widget when draggable is being dragged - feedback: peg(myProvider), - - // Widget to display on original place when being dragged - childWhenDragging: Container(), - - // Widget when draggable is stationary - child: peg(myProvider), - ); - } - - Widget peg(Data myProvider) { - String image = 'assets/skins/${myProvider.parameterSkin}_peg.png'; - - return Image( - image: AssetImage(image), - width: myProvider.tileSize, - height: myProvider.tileSize, - fit: BoxFit.fill, - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index e84151f467ffe84165623e79580b02b340e2aa35..d507c2d5d0495158dbf6f02d6a0fb3ba4a6faaa8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,17 +6,17 @@ 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:provider/provider.dart'; -import 'package:overlay_support/overlay_support.dart'; import 'package:solitaire/config/theme.dart'; -import 'package:solitaire/cubit/bottom_nav_cubit.dart'; +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/cubit/nav_cubit.dart'; +import 'package:solitaire/cubit/settings_game_cubit.dart'; +import 'package:solitaire/cubit/settings_global_cubit.dart'; import 'package:solitaire/cubit/theme_cubit.dart'; -import 'package:solitaire/provider/data.dart'; import 'package:solitaire/ui/skeleton.dart'; void main() async { - /// Initialize packages + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); final Directory tmpDir = await getTemporaryDirectory(); @@ -46,34 +46,28 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), + BlocProvider<NavCubit>(create: (context) => NavCubit()), BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()), + BlocProvider<GameCubit>(create: (context) => GameCubit()), + BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()), + BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()), ], child: BlocBuilder<ThemeCubit, ThemeModeState>( builder: (BuildContext context, ThemeModeState state) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>( - builder: (context, data, child) { - return OverlaySupport( - child: MaterialApp( - title: 'Solitaire', - home: const SkeletonScreen(), + return MaterialApp( + title: 'Solitaire', + home: const SkeletonScreen(), - // Theme stuff - theme: lightTheme, - darkTheme: darkTheme, - themeMode: state.themeMode, + // Theme stuff + theme: lightTheme, + darkTheme: darkTheme, + themeMode: state.themeMode, - // Localization stuff - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - ), - ); - }, - ), + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, ); }, ), diff --git a/lib/models/board.dart b/lib/models/board.dart new file mode 100644 index 0000000000000000000000000000000000000000..eb1ce73259a307e58116df681052bb81b551d6b2 --- /dev/null +++ b/lib/models/board.dart @@ -0,0 +1,169 @@ +import 'package:solitaire/data/game_data.dart'; +import 'package:solitaire/models/cell.dart'; +import 'package:solitaire/models/cell_location.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/utils/tools.dart'; + +typedef BoardCells = List<List<Cell>>; + +class Board { + Board({ + required this.cells, + }); + + BoardCells cells = const []; + + factory Board.createEmpty() { + return Board( + cells: [], + ); + } + + factory Board.createNew({ + required GameSettings gameSettings, + }) { + final List<String>? template = GameData.templates[gameSettings.layout]; + + final BoardCells grid = []; + + int row = 0; + template?.forEach((String line) { + final List<Cell> gridLine = []; + int col = 0; + line.split("").forEach((String tileCode) { + gridLine.add(tileCode == ' ' + ? Cell.none + : Cell( + location: CellLocation.go(row, col), + hasHole: true, + hasPeg: (tileCode == 'o'), + )); + col++; + }); + row++; + grid.add(gridLine); + }); + + return Board( + cells: grid, + ); + } + + int get boardSize => cells.length; + + Cell get(CellLocation location) { + if (location.row < cells.length) { + if (location.col < cells[location.row].length) { + return cells[location.row][location.col]; + } + } + + return Cell.none; + } + + void set(CellLocation location, Cell cell) { + cells[location.row][location.col] = cell; + } + + bool isMoveAllowed({ + required List<int> source, + required List<int> target, + }) { + // printlog('(test) Pick from ' + source.toString() + ' and drop on ' + target.toString()); + final int sourceCol = source[0]; + final int sourceRow = source[1]; + final int targetCol = target[0]; + final int targetRow = target[1]; + + // ensure source and target are inside range + if (sourceRow < 0 || + sourceRow > (boardSize - 1) || + sourceCol < 0 || + sourceCol > (boardSize - 1)) { + // printlog('move forbidden: source is out of board'); + return false; + } + if (targetRow < 0 || + targetRow > (boardSize - 1) || + targetCol < 0 || + targetCol > (boardSize - 1)) { + // printlog('move forbidden: target is out of board'); + return false; + } + + // ensure source exists and has a peg + if (cells[sourceRow][sourceCol].hasPeg == false) { + // printlog('move forbidden: source peg does not exist'); + return false; + } + + // ensure target exists and is empty + if (cells[targetRow][targetCol].hasPeg == true) { + // printlog('move forbidden: target does not exist or already with a peg'); + return false; + } + + // ensure source and target are in the same line/column + if ((targetCol != sourceCol) && (targetRow != sourceRow)) { + // printlog('move forbidden: source and target are not in the same line or column'); + return false; + } + + // ensure source and target are separated by exactly one tile + if (((targetCol == sourceCol) && ((targetRow - sourceRow).abs() != 2)) || + ((targetRow == sourceRow) && ((targetCol - sourceCol).abs() != 2))) { + // printlog('move forbidden: source and target must be separated by exactly one tile'); + return false; + } + + // ensure middle tile exists and has a peg + final int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round(); + final int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round(); + if (cells[middleRow][middleCol].hasPeg == false) { + // printlog('move forbidden: tile between source and target does not contain a peg'); + return false; + } + + // ok, move is allowed + return true; + } + + void printGrid() { + String textBoard = ' '; + String textHole = '·'; + String textPeg = 'o'; + + printlog(''); + printlog('-------'); + for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { + String row = ''; + for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + String textCell = textBoard; + Cell tile = cells[rowIndex][colIndex]; + textCell = tile.hasPeg ? textPeg : textHole; + row += textCell; + } + printlog(row); + } + printlog('-------'); + printlog(''); + } + + void dump() { + printlog(''); + printlog('$Board:'); + printGrid(); + printlog(''); + } + + @override + String toString() { + return '$Board(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'cells': cells, + }; + } +} diff --git a/lib/models/cell.dart b/lib/models/cell.dart new file mode 100644 index 0000000000000000000000000000000000000000..127c3006372ae606f25206aa7ef1209ec78a1ecc --- /dev/null +++ b/lib/models/cell.dart @@ -0,0 +1,41 @@ +import 'package:solitaire/models/cell_location.dart'; +import 'package:solitaire/utils/tools.dart'; + +class Cell { + Cell({ + required this.location, + required this.hasHole, + required this.hasPeg, + }); + + final CellLocation location; + bool hasHole; + bool hasPeg; + + static Cell none = Cell( + location: CellLocation.go(0, 0), + hasHole: false, + hasPeg: false, + ); + + void dump() { + printlog('$Cell:'); + printlog(' location: $location'); + printlog(' hasHole: $hasHole'); + printlog(' hasPeg: $hasPeg'); + printlog(''); + } + + @override + String toString() { + return '$Cell(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'location': location, + 'hasHole': hasHole, + 'hasPeg': hasPeg, + }; + } +} diff --git a/lib/models/cell_location.dart b/lib/models/cell_location.dart new file mode 100644 index 0000000000000000000000000000000000000000..f9b90220feff4828ca6fa846d4933cac195608b2 --- /dev/null +++ b/lib/models/cell_location.dart @@ -0,0 +1,34 @@ +import 'package:solitaire/utils/tools.dart'; + +class CellLocation { + final int col; + final int row; + + CellLocation({ + required this.col, + required this.row, + }); + + factory CellLocation.go(int row, int col) { + return CellLocation(col: col, row: row); + } + + void dump() { + printlog('$CellLocation:'); + printlog(' col: $col'); + printlog(' row: $row'); + printlog(''); + } + + @override + String toString() { + return '$CellLocation(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'col': col, + 'row': row, + }; + } +} diff --git a/lib/models/game.dart b/lib/models/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..009ff4f3b07ce3370c5b6764f89e8c3f2256fea2 --- /dev/null +++ b/lib/models/game.dart @@ -0,0 +1,142 @@ +import 'package:solitaire/models/board.dart'; +import 'package:solitaire/models/cell.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/models/settings_global.dart'; +import 'package:solitaire/utils/tools.dart'; + +class Game { + Game({ + required this.gameSettings, + required this.globalSettings, + required this.board, + this.isRunning = false, + this.isFinished = false, + this.movesCount = 0, + this.remainingPegsCount = 0, + this.allowedMovesCount = 0, + }); + + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + bool isRunning = false; + bool isFinished = false; + + Board board; + + int movesCount = 0; + int remainingPegsCount = 0; + int allowedMovesCount = 0; + + factory Game.createNull() { + return Game( + gameSettings: GameSettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), + board: Board.createEmpty(), + ); + } + + factory Game.createNew({ + GameSettings? gameSettings, + GlobalSettings? globalSettings, + }) { + GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); + GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + return Game( + gameSettings: newGameSettings, + globalSettings: newGlobalSettings, + board: Board.createNew( + gameSettings: newGameSettings, + ), + isRunning: true, + ); + } + + int get boardSize => board.boardSize; + + bool get gameWon => (isFinished && (remainingPegsCount == 1)); + + List<Cell> listRemainingPegs() { + final List<Cell> pegs = []; + + final BoardCells cells = board.cells; + for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { + for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + Cell tile = cells[rowIndex][colIndex]; + if (tile.hasPeg == true) { + pegs.add(tile); + } + } + } + + return pegs; + } + + int countRemainingPegs() { + return listRemainingPegs().length; + } + + int countAllowedMoves() { + int allowedMovesCount = 0; + final List<Cell> pegs = listRemainingPegs(); + for (Cell tile in pegs) { + final int row = tile.location.row; + final int col = tile.location.col; + final List<int> source = [col, row]; + final List<List<int>> targets = [ + [col - 2, row], + [col + 2, row], + [col, row - 2], + [col, row + 2], + ]; + for (List<int> target in targets) { + if (board.isMoveAllowed( + source: source, + target: target, + )) { + allowedMovesCount++; + } + } + } + + return allowedMovesCount; + } + + void dump() { + printlog(''); + printlog('## Current game dump:'); + printlog(''); + gameSettings.dump(); + globalSettings.dump(); + printlog(''); + printlog(''); + printlog('$Game: '); + printlog(' isRunning: $isRunning'); + printlog(' isFinished: $isFinished'); + printlog(' movesCount: $movesCount'); + printlog(' remainingPegsCount: $remainingPegsCount'); + printlog(' allowedMovesCount: $allowedMovesCount'); + printlog(''); + board.dump(); + printlog(''); + } + + @override + String toString() { + return '$Game(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'gameSettings': gameSettings.toJson(), + 'globalSettings': globalSettings.toJson(), + 'board': board.toJson(), + 'isRunning': isRunning, + 'isFinished': isFinished, + 'movesCount': movesCount, + 'remainingPegsCount': remainingPegsCount, + 'allowedMovesCount': allowedMovesCount, + }; + } +} diff --git a/lib/models/settings_game.dart b/lib/models/settings_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..cb00bc94a4ce5dab5065076202e0215e1cd21b97 --- /dev/null +++ b/lib/models/settings_game.dart @@ -0,0 +1,41 @@ +import 'package:solitaire/config/default_game_settings.dart'; +import 'package:solitaire/utils/tools.dart'; + +class GameSettings { + final String layout; + + GameSettings({ + required this.layout, + }); + + static String getLayoutValueFromUnsafe(String layout) { + if (DefaultGameSettings.allowedLayoutValues.contains(layout)) { + return layout; + } + + return DefaultGameSettings.defaultLayoutValue; + } + + factory GameSettings.createDefault() { + return GameSettings( + layout: DefaultGameSettings.defaultLayoutValue, + ); + } + + void dump() { + printlog('$GameSettings: '); + printlog(' ${DefaultGameSettings.parameterCodeLayout}: $layout'); + printlog(''); + } + + @override + String toString() { + return '$GameSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeLayout: layout, + }; + } +} diff --git a/lib/models/settings_global.dart b/lib/models/settings_global.dart new file mode 100644 index 0000000000000000000000000000000000000000..dd4b7c86673d9ed4e43f5cdbb0f8e9a0bd10f851 --- /dev/null +++ b/lib/models/settings_global.dart @@ -0,0 +1,41 @@ +import 'package:solitaire/config/default_global_settings.dart'; +import 'package:solitaire/utils/tools.dart'; + +class GlobalSettings { + String skin; + + GlobalSettings({ + required this.skin, + }); + + static String getSkinValueFromUnsafe(String skin) { + if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) { + return skin; + } + + return DefaultGlobalSettings.defaultSkinValue; + } + + factory GlobalSettings.createDefault() { + return GlobalSettings( + skin: DefaultGlobalSettings.defaultSkinValue, + ); + } + + void dump() { + printlog('$GlobalSettings: '); + printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin'); + printlog(''); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: skin, + }; + } +} diff --git a/lib/models/types.dart b/lib/models/types.dart new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index 9c18e794f5d5eb7045d31de5a7a7c3b26582a444..0000000000000000000000000000000000000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,239 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:solitaire/entities/tile.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -typedef Board = List<List<Tile?>>; - -class Data extends ChangeNotifier { - // Configuration available values - final List<String> _availableParameters = ['layout', 'skin']; - - final List<String> _availableLayoutValues = ['french', 'german', 'english', 'diamond']; - final List<String> _availableSkinValues = ['default']; - - List<String> get availableParameters => _availableParameters; - List<String> get availableLayoutValues => _availableLayoutValues; - List<String> get availableSkinValues => _availableSkinValues; - - // Application default configuration - String _parameterLayout = ''; - String _parameterSkin = ''; - final String _parameterLayoutDefault = 'english'; - final String _parameterSkinDefault = 'default'; - - // Application current configuration - String get parameterLayout => _parameterLayout; - String get parameterSkin => _parameterSkin; - - // Game data - bool _assetsPreloaded = false; - bool _gameIsRunning = false; - bool _gameIsFinished = false; - Board _board = []; - int _boardSize = 0; - double _tileSize = 0; - int _movesCount = 0; - int _remainingPegsCount = 0; - int _allowedMovesCount = 0; - String _currentState = ''; - - void updateParameterLayout(String parameterLayout) { - _parameterLayout = parameterLayout; - notifyListeners(); - } - - void updateParameterSkin(String parameterSkin) { - _parameterSkin = parameterSkin; - notifyListeners(); - } - - String getParameterValue(String parameterCode) { - switch (parameterCode) { - case 'layout': - return _parameterLayout; - case 'skin': - return _parameterSkin; - } - return ''; - } - - List getParameterAvailableValues(String parameterCode) { - switch (parameterCode) { - case 'layout': - return _availableLayoutValues; - case 'skin': - return _availableSkinValues; - } - return []; - } - - void setParameterValue(String parameterCode, String parameterValue) async { - switch (parameterCode) { - case 'layout': - updateParameterLayout(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('layout', prefs.getString('layout') ?? _parameterLayoutDefault); - setParameterValue('skin', prefs.getString('skin') ?? _parameterSkinDefault); - } - - String get currentState => _currentState; - - String computeCurrentGameState() { - String boardValues = ''; - - String textBoard = ' '; - String textHole = '·'; - String textPeg = 'o'; - for (int rowIndex = 0; rowIndex < _board.length; rowIndex++) { - for (int colIndex = 0; colIndex < _board[rowIndex].length; colIndex++) { - String cellValue = textBoard; - if (_board[rowIndex][colIndex] != null) { - cellValue = (_board[rowIndex][colIndex]?.hasPeg ?? false) ? textPeg : textHole; - } - boardValues += cellValue; - } - } - - var currentState = { - 'layout': _parameterLayout, - 'skin': _parameterSkin, - 'movesCount': _movesCount.toString(), - 'boardValues': boardValues, - }; - - 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 != '') { - final Map<String, dynamic> savedState = json.decode(_currentState); - if (savedState.isNotEmpty) { - return savedState; - } - } - return {}; - } - - bool get assetsPreloaded => _assetsPreloaded; - void updateAssetsPreloaded(bool assetsPreloaded) { - _assetsPreloaded = assetsPreloaded; - } - - double get tileSize => _tileSize; - void updateTileSize(double tileSize) { - _tileSize = tileSize; - } - - int get boardSize => _boardSize; - void updateBoardSize(int boardSize) { - _boardSize = boardSize; - } - - Board get board => _board; - void updateBoard(Board board) { - _board = board; - updateBoardSize(board.length); - updateRemainingPegsCount(GameUtils.countRemainingPegs(this)); - updateAllowedMovesCount(GameUtils.countAllowedMoves(this)); - notifyListeners(); - } - - updatePegValue(int row, int col, bool hasPeg) { - if (_board[row][col] != null) { - _board[row][col]?.hasPeg = hasPeg; - - saveCurrentGameState(); - notifyListeners(); - } - } - - int get movesCount => _movesCount; - void updateMovesCount(int movesCount) { - _movesCount = movesCount; - notifyListeners(); - } - - void incrementMovesCount() { - updateMovesCount(movesCount + 1); - } - - int get remainingPegsCount => _remainingPegsCount; - void updateRemainingPegsCount(int remainingPegsCount) { - _remainingPegsCount = remainingPegsCount; - notifyListeners(); - } - - int get allowedMovesCount => _allowedMovesCount; - void updateAllowedMovesCount(int allowedMovesCount) { - _allowedMovesCount = allowedMovesCount; - if (allowedMovesCount == 0) { - updateGameIsFinished(true); - } - notifyListeners(); - } - - bool get gameIsRunning => _gameIsRunning; - bool get gameIsFinished => _gameIsFinished; - void updateGameIsRunning(bool gameIsRunning) { - _gameIsRunning = gameIsRunning; - notifyListeners(); - } - - void updateGameIsFinished(bool gameIsFinished) { - _gameIsFinished = gameIsFinished; - notifyListeners(); - } - - bool gameWon() { - return gameIsFinished && (remainingPegsCount == 1); - } - - void resetGame() { - _gameIsRunning = false; - _gameIsFinished = false; - _movesCount = 0; - _remainingPegsCount = 0; - notifyListeners(); - } -} diff --git a/lib/ui/layout/game.dart b/lib/ui/layout/game.dart deleted file mode 100644 index aa9d47c9fdf68e5c14eff6121c3108ceb10376bb..0000000000000000000000000000000000000000 --- a/lib/ui/layout/game.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/layout/tileset.dart'; -import 'package:solitaire/ui/widgets/game/indicator_top.dart'; -import 'package:solitaire/ui/widgets/game/message_game_end.dart'; - -class Game extends StatelessWidget { - const Game({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final bool gameIsFinished = myProvider.gameIsFinished; - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 8), - TopIndicator(myProvider: myProvider), - const SizedBox(height: 2), - Expanded( - child: Tileset(myProvider: myProvider), - ), - const SizedBox(height: 2), - Container( - child: gameIsFinished - ? EndGameMessage(myProvider: myProvider) - : const SizedBox(height: 2), - ), - ], - ); - } -} diff --git a/lib/ui/layout/parameters.dart b/lib/ui/layout/parameters.dart deleted file mode 100644 index 68d9ac836bb4c5bfc6c5017ec291fe3da4819f03..0000000000000000000000000000000000000000 --- a/lib/ui/layout/parameters.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/widgets/home/button_game_resume.dart'; -import 'package:solitaire/ui/widgets/home/button_game_start_new.dart'; - -class Parameters extends StatelessWidget { - const Parameters({super.key, required this.myProvider}); - - final Data myProvider; - - static const double separatorHeight = 2.0; - static const double blockMargin = 0.0; - static const double blockPadding = 0.0; - static const Color buttonBackgroundColor = Colors.white; - static const Color buttonBorderColorActive = Colors.blue; - static const Color buttonBorderColorInactive = Colors.white; - static const double buttonBorderWidth = 6.0; - static const double buttonBorderRadius = 8.0; - static const double buttonPadding = 0.0; - static const double buttonMargin = 0.0; - - @override - Widget build(BuildContext context) { - List<Widget> lines = []; - - List parameters = myProvider.availableParameters; - for (int index = 0; index < parameters.length; index++) { - lines.add(buildParameterSelector(myProvider, parameters[index])); - lines.add(const SizedBox(height: separatorHeight)); - } - - myProvider.loadCurrentSavedState(); - Widget buttonsBlock = myProvider.hasCurrentSavedState() - ? ResumeGameButton(myProvider: myProvider) - : StartNewGameButton(myProvider: myProvider); - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: separatorHeight), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: lines, - ), - ), - const 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: () {}, - ), - ], - ); - } - - Widget buildParameterSelector(Data myProvider, String parameterCode) { - List availableValues = myProvider.getParameterAvailableValues(parameterCode); - - if (availableValues.length == 1) { - return const SizedBox(height: 0.0); - } - - return Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - for (int index = 0; index < availableValues.length; index++) - Column( - children: [ - buildParameterButton(myProvider, parameterCode, availableValues[index]) - ], - ), - ], - ), - ], - ); - } - - 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: const EdgeInsets.all(buttonMargin), - padding: const 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/ui/layout/tileset.dart b/lib/ui/layout/tileset.dart deleted file mode 100644 index 4fe85a743bfc7cb8ccb3035a3dddd9c32d6b210c..0000000000000000000000000000000000000000 --- a/lib/ui/layout/tileset.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; - -class Tileset extends StatelessWidget { - const Tileset({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final Board board = myProvider.board; - - Widget boardTileWithoutHole = Image( - image: AssetImage('assets/skins/${myProvider.parameterSkin}_board.png'), - width: myProvider.tileSize, - height: myProvider.tileSize, - fit: BoxFit.fill, - ); - - return Column( - children: [ - Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - for (int row = 0; row < board.length; row++) - TableRow( - children: [ - for (int col = 0; col < board[row].length; col++) - TableCell( - child: board[row][col] != null - ? (board[row][col]?.render(myProvider) ?? Container()) - : boardTileWithoutHole, - ), - ], - ), - ], - ), - ], - ); - } -} diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart new file mode 100644 index 0000000000000000000000000000000000000000..4ed92d7734eb935bbe24925a49fcab2864476d33 --- /dev/null +++ b/lib/ui/painters/parameter_painter.dart @@ -0,0 +1,190 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:solitaire/config/default_game_settings.dart'; +import 'package:solitaire/data/game_data.dart'; +import 'package:solitaire/models/board.dart'; +import 'package:solitaire/models/cell.dart'; +import 'package:solitaire/models/cell_location.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/models/settings_global.dart'; +import 'package:solitaire/utils/tools.dart'; + +class ParameterPainter extends CustomPainter { + const ParameterPainter({ + required this.code, + required this.value, + required this.isSelected, + required this.gameSettings, + required this.globalSettings, + }); + + final String code; + final String value; + final bool isSelected; + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + @override + void paint(Canvas canvas, Size size) { + // force square + final double canvasSize = min(size.width, size.height); + + const Color borderColorEnabled = Colors.blue; + const Color borderColorDisabled = Colors.white; + + // "enabled/disabled" border + final paint = Paint(); + paint.style = PaintingStyle.stroke; + paint.color = isSelected ? borderColorEnabled : borderColorDisabled; + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 10; + canvas.drawRect( + Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint); + + // content + switch (code) { + case DefaultGameSettings.parameterCodeLayout: + paintLayoutParameterItem(value, canvas, canvasSize); + break; + default: + printlog('Unknown parameter: $code/$value'); + paintUnknownParameterItem(value, canvas, canvasSize); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } + + // "unknown" parameter -> simple block with text + void paintUnknownParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + paint.color = Colors.grey; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + final textSpan = TextSpan( + text: '?\n$value', + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } + + void paintLayoutParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + { + const Color backgroundColor = Colors.white; + + Color gridBackgroundColor = Colors.grey; + + switch (value) { + case DefaultGameSettings.layoutValueFrench: + gridBackgroundColor = Colors.green; + break; + case DefaultGameSettings.layoutValueGerman: + gridBackgroundColor = Colors.orange; + break; + case DefaultGameSettings.layoutValueEnglish: + gridBackgroundColor = Colors.red; + break; + case DefaultGameSettings.layoutValueDiamond: + gridBackgroundColor = Colors.purple; + break; + default: + printlog('Wrong value for size parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Mini grid + final borderColor = Colors.grey.shade800; + + // Get board data from named templates + final List<String>? template = GameData.templates[value]; + final BoardCells grid = []; + + int row = 0; + template?.forEach((String line) { + final List<Cell> gridLine = []; + int col = 0; + line.split("").forEach((String tileCode) { + gridLine.add(tileCode == ' ' + ? Cell.none + : Cell( + location: CellLocation.go(row, col), + hasHole: true, + hasPeg: (tileCode == 'o'), + )); + col++; + }); + row++; + grid.add(gridLine); + }); + + final int gridWidth = grid.length; + final int gridHeight = grid.first.length; + paint.strokeWidth = 2; + + final double cellSize = (size * .9) / max(gridWidth, gridHeight); + final double originX = (size - gridWidth * cellSize) / 2; + final double originY = (size - gridHeight * cellSize) / 2; + + for (int row = 0; row < gridHeight; row++) { + for (int col = 0; col < gridWidth; col++) { + final Offset topLeft = Offset(originX + col * cellSize, originY + row * cellSize); + final Offset bottomRight = topLeft + Offset(cellSize, cellSize); + + paint.color = Colors.white; + paint.style = PaintingStyle.fill; + + if (grid[row][col].hasPeg) { + paint.color = gridBackgroundColor; + } + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + if (grid[row][col].hasHole) { + paint.color = borderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + } + } + } + } +} diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart deleted file mode 100644 index 9171fc2ac981a36123338eedce6cd09105852e5f..0000000000000000000000000000000000000000 --- a/lib/ui/screens/game_page.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/layout/game.dart'; -import 'package:solitaire/ui/layout/parameters.dart'; - -class GamePage extends StatefulWidget { - const GamePage({super.key}); - - @override - GamePageState createState() => GamePageState(); -} - -class GamePageState extends State<GamePage> { - @override - void initState() { - super.initState(); - - final Data myProvider = Provider.of<Data>(context, listen: false); - myProvider.initParametersValues(); - myProvider.loadCurrentSavedState(); - } - - List<String> getImagesAssets(Data myProvider) { - final List<String> assets = []; - - final List<String> gameImages = [ - 'button_back', - 'button_delete_saved_game', - 'button_resume_game', - 'button_start', - 'game_fail', - 'game_win', - 'placeholder', - ]; - for (String layout in myProvider.availableLayoutValues) { - gameImages.add('layout_$layout'); - } - for (String skin in myProvider.availableSkinValues) { - gameImages.add('skin_$skin'); - } - - for (String image in gameImages) { - assets.add('${'assets/icons/$image'}.png'); - } - - const List<String> skinImages = [ - 'board', - 'hole', - 'peg', - ]; - - for (String skin in myProvider.availableSkinValues) { - for (String image in skinImages) { - assets.add('${'${'assets/skins/$skin'}_$image'}.png'); - } - } - - return assets; - } - - @override - Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - - if (!myProvider.assetsPreloaded) { - final List<String> assets = getImagesAssets(myProvider); - for (String asset in assets) { - precacheImage(AssetImage(asset), context); - } - myProvider.updateAssetsPreloaded(true); - } - - myProvider.updateTileSize((MediaQuery.of(context).size.width - 40) / myProvider.boardSize); - - return SafeArea( - child: Center( - child: myProvider.gameIsRunning - ? Game(myProvider: myProvider) - : Parameters(myProvider: myProvider), - ), - ); - } -} diff --git a/lib/ui/screens/about_page.dart b/lib/ui/screens/page_about.dart similarity index 89% rename from lib/ui/screens/about_page.dart rename to lib/ui/screens/page_about.dart index f22e2adbcf2c3fbf29b74937ef251a5ab64db479..f63e1530869dd9aaf2a093e2610f326c3050589f 100644 --- a/lib/ui/screens/about_page.dart +++ b/lib/ui/screens/page_about.dart @@ -2,10 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:solitaire/ui/widgets/header_app.dart'; +import 'package:solitaire/ui/widgets/helpers/app_header.dart'; -class AboutPage extends StatelessWidget { - const AboutPage({super.key}); +class PageAbout extends StatelessWidget { + const PageAbout({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..287a3832dc66424363f99183602f6b3faf4785fd --- /dev/null +++ b/lib/ui/screens/page_game.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/ui/widgets/game/game_widget.dart'; +import 'package:solitaire/ui/widgets/parameters.dart'; + +class PageGame extends StatelessWidget { + const PageGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return gameState.currentGame.isRunning ? const GameWidget() : const Parameters(); + }, + ); + } +} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/page_settings.dart similarity index 80% rename from lib/ui/screens/settings_page.dart rename to lib/ui/screens/page_settings.dart index 63cb3f2c502e952d128108dceff49b16dce7a10d..ae65ab921d0840b9d014f098187ae4b02076a9b2 100644 --- a/lib/ui/screens/settings_page.dart +++ b/lib/ui/screens/page_settings.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:solitaire/ui/widgets/header_app.dart'; +import 'package:solitaire/ui/widgets/helpers/app_header.dart'; import 'package:solitaire/ui/widgets/settings/settings_form.dart'; -class SettingsPage extends StatelessWidget { - const SettingsPage({super.key}); +class PageSettings extends StatelessWidget { + const PageSettings({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index 36cd48520de4304219d8273481aecd572b181e5f..c57f5c2ce6db287ee0efbcde7f0f0dfd7afd59fd 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,32 +1,33 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; import 'package:solitaire/config/menu.dart'; -import 'package:solitaire/cubit/bottom_nav_cubit.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/widgets/app_bar.dart'; -import 'package:solitaire/ui/widgets/bottom_nav_bar.dart'; +import 'package:solitaire/cubit/nav_cubit.dart'; +import 'package:solitaire/ui/widgets/global_app_bar.dart'; -class SkeletonScreen extends StatefulWidget { +class SkeletonScreen extends StatelessWidget { const SkeletonScreen({super.key}); - @override - State<SkeletonScreen> createState() => _SkeletonScreenState(); -} - -class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - return Scaffold( + appBar: const GlobalAppBar(), extendBodyBehindAppBar: false, - appBar: StandardAppBar(myProvider: myProvider), - bottomNavigationBar: const BottomNavBar(), - body: BlocBuilder<BottomNavCubit, int>(builder: (BuildContext context, int state) { - return Menu.getPageWidget(state); - }), + body: Material( + color: Theme.of(context).colorScheme.background, + child: BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + return Padding( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), + child: Menu.getPageWidget(pageIndex), + ); + }, + ), + ), backgroundColor: Theme.of(context).colorScheme.background, ); } diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart deleted file mode 100644 index 1dc274e7c430ae5822e3c01d400e16ee1844227f..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/app_bar.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:overlay_support/overlay_support.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/widgets/header_app.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class StandardAppBar extends StatelessWidget implements PreferredSizeWidget { - const StandardAppBar({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final List<Widget> menuActions = []; - - if (myProvider.gameIsRunning) { - menuActions.add(TextButton( - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () => toast(tr('long_press_to_quit')), - onLongPress: () => GameUtils.quitGame(myProvider), - )); - } - - return AppBar( - title: const AppHeader(text: 'app_name'), - actions: menuActions, - ); - } - - @override - Size get preferredSize => const Size.fromHeight(50); -} diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart deleted file mode 100644 index 7e3d4717dfcaff4a58173b18531da62361a389b8..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:solitaire/config/menu.dart'; -import 'package:solitaire/cubit/bottom_nav_cubit.dart'; - -class BottomNavBar extends StatelessWidget { - const BottomNavBar({super.key}); - - @override - Widget build(BuildContext context) { - return Card( - margin: const EdgeInsets.all(0), - elevation: 4, - shadowColor: Theme.of(context).colorScheme.shadow, - color: Theme.of(context).colorScheme.surfaceVariant, - shape: const ContinuousRectangleBorder(), - child: BlocBuilder<BottomNavCubit, int>( - builder: (BuildContext context, int state) { - return BottomNavigationBar( - currentIndex: state, - onTap: (int index) { - context.read<BottomNavCubit>().updateIndex(index); - }, - type: BottomNavigationBarType.fixed, - elevation: 0, - backgroundColor: Colors.transparent, - selectedItemColor: Theme.of(context).colorScheme.primary, - unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color, - items: Menu.getMenuItems(), - ); - }, - ), - ); - } -} diff --git a/lib/ui/widgets/button_game_start_new.dart b/lib/ui/widgets/button_game_start_new.dart new file mode 100644 index 0000000000000000000000000000000000000000..cd4b8f01caebb1ed5195e2e702b62d01806cfc01 --- /dev/null +++ b/lib/ui/widgets/button_game_start_new.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/cubit/settings_game_cubit.dart'; +import 'package:solitaire/cubit/settings_global_cubit.dart'; + +class StartNewGameButton extends StatelessWidget { + const StartNewGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + return TextButton( + child: const Image( + image: AssetImage('assets/icons/button_start.png'), + fit: BoxFit.fill, + ), + onPressed: () => gameCubit.startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/game_widget.dart b/lib/ui/widgets/game/game_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..e2c7cafae7bcbca07d3309d47a967452329e4b1b --- /dev/null +++ b/lib/ui/widgets/game/game_widget.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/game.dart'; +import 'package:solitaire/ui/widgets/game/indicator_top.dart'; +import 'package:solitaire/ui/widgets/game/message_game_end.dart'; +import 'package:solitaire/ui/widgets/game/tileset.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; + + final bool gameIsFinished = currentGame.isFinished; + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 8), + const TopIndicator(), + const SizedBox(height: 2), + const Expanded( + child: Tileset(), + ), + const SizedBox(height: 2), + gameIsFinished ? const EndGameMessage() : const SizedBox(height: 2), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/indicator_top.dart b/lib/ui/widgets/game/indicator_top.dart index 7ce0c3ef4fe6cfeab481697472e822a1823768cd..bc66f795fb1c03308aa1a4492367d87c9cb89dd4 100644 --- a/lib/ui/widgets/game/indicator_top.dart +++ b/lib/ui/widgets/game/indicator_top.dart @@ -1,47 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:solitaire/provider/data.dart'; +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/game.dart'; class TopIndicator extends StatelessWidget { - const TopIndicator({super.key, required this.myProvider}); - - final Data myProvider; + const TopIndicator({super.key}); @override Widget build(BuildContext context) { - final int allowedMovesCount = myProvider.allowedMovesCount; + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; - return Table( - children: [ - TableRow( + return Table( children: [ - Column( + TableRow( children: [ - Text( - '♟️ ${myProvider.remainingPegsCount}', - style: const TextStyle( - fontSize: 40, - fontWeight: FontWeight.w600, - color: Colors.black, - ), + Column( + children: [ + Text( + '♟️ ${currentGame.remainingPegsCount}', + style: const TextStyle( + fontSize: 40, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ], ), - ], - ), - Column( - children: [ - Text( - allowedMovesCount.toString(), - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.green, - ), + Column( + children: [ + Text( + currentGame.allowedMovesCount.toString(), + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.green, + ), + ), + ], ), ], ), ], - ), - ], + ); + }, ); } } diff --git a/lib/ui/widgets/game/message_game_end.dart b/lib/ui/widgets/game/message_game_end.dart index cd6ca3ad64384a7cf48e93a3c91519bd04ffe06f..b1b4faa4d361a8c7ddff40d35028c381d2378c58 100644 --- a/lib/ui/widgets/game/message_game_end.dart +++ b/lib/ui/widgets/game/message_game_end.dart @@ -1,42 +1,56 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/widgets/home/button_game_restart.dart'; +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/game.dart'; class EndGameMessage extends StatelessWidget { - const EndGameMessage({super.key, required this.myProvider}); - - final Data myProvider; + const EndGameMessage({super.key}); @override Widget build(BuildContext context) { - String decorationImageAssetName = ''; - if (myProvider.gameWon()) { - decorationImageAssetName = 'assets/icons/game_win.png'; - } else { - decorationImageAssetName = 'assets/icons/placeholder.png'; - } + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; - final Image decorationImage = Image( - image: AssetImage(decorationImageAssetName), - fit: BoxFit.fill, - ); + String decorationImageAssetName = ''; + if (currentGame.gameWon) { + decorationImageAssetName = 'assets/icons/game_win.png'; + } else { + decorationImageAssetName = 'assets/icons/placeholder.png'; + } + + final Image decorationImage = Image( + image: AssetImage(decorationImageAssetName), + fit: BoxFit.fill, + ); - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), children: [ - Column(children: [decorationImage]), - Column(children: [RestartGameButton(myProvider: myProvider)]), - Column(children: [decorationImage]), + TableRow( + children: [ + Column(children: [decorationImage]), + TextButton( + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.quitGame(); + }, + ), + Column(children: [decorationImage]), + ], + ), ], ), - ], - ), + ); + }, ); } } diff --git a/lib/ui/widgets/game/tile_widget.dart b/lib/ui/widgets/game/tile_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..7d71df58bb998882634777b00081a0ce99d0be6a --- /dev/null +++ b/lib/ui/widgets/game/tile_widget.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/cell.dart'; +import 'package:solitaire/models/game.dart'; + +class TileWidget extends StatelessWidget { + const TileWidget({ + super.key, + required this.tile, + required this.tileSize, + }); + + final Cell tile; + final double tileSize; + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + if (tile.hasHole) { + return render( + currentGame: currentGame, + gameCubit: gameCubit, + ); + } else { + return Image( + image: AssetImage('assets/skins/${currentGame.globalSettings.skin}_board.png'), + width: tileSize, + height: tileSize, + fit: BoxFit.fill, + ); + } + }, + ); + } + + Widget render({ + required Game currentGame, + required GameCubit gameCubit, + }) { + List<Widget> stack = [ + hole( + currentGame: currentGame, + gameCubit: gameCubit, + ), + ]; + + if (tile.hasPeg) { + stack.add(draggable( + currentGame: currentGame, + )); + } + + return Stack( + alignment: Alignment.center, + children: stack, + ); + } + + Widget hole({ + required Game currentGame, + required GameCubit gameCubit, + }) { + String image = 'assets/skins/${currentGame.globalSettings.skin}_hole.png'; + + return DragTarget<List<int>>( + builder: ( + BuildContext context, + List<dynamic> accepted, + List<dynamic> rejected, + ) { + return Image( + image: AssetImage(image), + width: tileSize, + height: tileSize, + fit: BoxFit.fill, + ); + }, + onAcceptWithDetails: (DragTargetDetails<List<int>> source) { + List<int> target = [tile.location.col, tile.location.row]; + // printlog('(drag) Pick from ' + source.toString() + ' and drop on ' + target.toString()); + if (currentGame.board.isMoveAllowed( + source: source.data, + target: target, + )) { + gameCubit.move( + currentGame: currentGame, + source: source.data, + target: target, + ); + } + }, + ); + } + + Widget draggable({ + required Game currentGame, + }) { + return Draggable<List<int>>( + data: [tile.location.col, tile.location.row], + + // Widget when draggable is being dragged + feedback: peg( + currentGame: currentGame, + ), + + // Widget to display on original place when being dragged + childWhenDragging: Container(), + + // Widget when draggable is stationary + child: peg( + currentGame: currentGame, + ), + ); + } + + Widget peg({ + required Game currentGame, + }) { + String image = 'assets/skins/${currentGame.globalSettings.skin}_peg.png'; + + return Image( + image: AssetImage(image), + width: tileSize, + height: tileSize, + fit: BoxFit.fill, + ); + } +} diff --git a/lib/ui/widgets/game/tileset.dart b/lib/ui/widgets/game/tileset.dart new file mode 100644 index 0000000000000000000000000000000000000000..5462a796ad34ad7fc042d41dea00fabc0faf35fd --- /dev/null +++ b/lib/ui/widgets/game/tileset.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/board.dart'; +import 'package:solitaire/ui/widgets/game/tile_widget.dart'; + +class Tileset extends StatelessWidget { + const Tileset({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Board board = gameState.currentGame.board; + final BoardCells cells = board.cells; + + final boardSize = gameState.currentGame.boardSize; + final double tileSize = (MediaQuery.of(context).size.width - 40) / boardSize; + + return Column( + children: [ + Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + for (int row = 0; row < cells.length; row++) + TableRow( + children: [ + for (int col = 0; col < cells[row].length; col++) + TableCell( + child: TileWidget( + tile: cells[row][col], + tileSize: tileSize, + ), + ), + ], + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..82121035102232012623d4532250a5c551412535 --- /dev/null +++ b/lib/ui/widgets/global_app_bar.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/config/menu.dart'; +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/cubit/nav_cubit.dart'; +import 'package:solitaire/models/game.dart'; +import 'package:solitaire/ui/widgets/helpers/app_title.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) { + return BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + final Game currentGame = gameState.currentGame; + + final List<Widget> menuActions = []; + + if (currentGame.isRunning) { + menuActions.add(TextButton( + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () {}, + onLongPress: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.quitGame(); + }, + )); + } else { + if (pageIndex == Menu.indexGame) { + // go to Settings page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToSettingsPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemSettings.icon, + )); + + // go to About page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToAboutPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemAbout.icon, + )); + } else { + // back to Home page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToGamePage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemGame.icon, + )); + } + } + + return AppBar( + title: const AppTitle(text: 'app_name'), + actions: menuActions, + ); + }, + ); + }, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/helpers/app_header.dart similarity index 77% rename from lib/ui/widgets/header_app.dart rename to lib/ui/widgets/helpers/app_header.dart index bf54b77375fbd0260f876f2885d0572b71715383..b5c5be05f6636cf488dcdb5bbc4d6f049b98de11 100644 --- a/lib/ui/widgets/header_app.dart +++ b/lib/ui/widgets/helpers/app_header.dart @@ -8,15 +8,16 @@ class AppHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( + return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( tr(text), textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + style: Theme.of(context).textTheme.headlineSmall!.apply(fontWeightDelta: 2), ), + const SizedBox(height: 8), ], ); } diff --git a/lib/ui/widgets/helpers/app_title.dart b/lib/ui/widgets/helpers/app_title.dart new file mode 100644 index 0000000000000000000000000000000000000000..7cbbb2030419047b3dcf093a2195a498bd8e8ce9 --- /dev/null +++ b/lib/ui/widgets/helpers/app_title.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/home/button_game_restart.dart b/lib/ui/widgets/home/button_game_restart.dart deleted file mode 100644 index 48dfc3438732370f607b916c7f15a4c1b6968673..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/home/button_game_restart.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class RestartGameButton extends StatelessWidget { - const RestartGameButton({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - return TextButton( - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () => GameUtils.quitAndDeleteCurrentGame(myProvider), - ); - } -} diff --git a/lib/ui/widgets/home/button_game_resume.dart b/lib/ui/widgets/home/button_game_resume.dart deleted file mode 100644 index 185c92f9c1fc6d607c9d947804700a74f8ae4a84..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/home/button_game_resume.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/ui/layout/parameters.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class ResumeGameButton extends Parameters { - const ResumeGameButton({super.key, required super.myProvider}); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(Parameters.blockMargin), - padding: const EdgeInsets.all(Parameters.blockPadding), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_delete_saved_game'), - onPressed: () => GameUtils.deleteSavedGame(myProvider), - ), - ], - ), - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_resume_game'), - onPressed: () => GameUtils.resumeSavedGame(myProvider), - ), - ], - ), - Parameters.buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/home/button_game_start_new.dart b/lib/ui/widgets/home/button_game_start_new.dart deleted file mode 100644 index 2de0c55bc99e5830965595bde7dd09617add80a2..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/home/button_game_start_new.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/layout/parameters.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class StartNewGameButton extends StatelessWidget { - const StartNewGameButton({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(Parameters.blockMargin), - padding: const EdgeInsets.all(Parameters.blockPadding), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Parameters.buildDecorationImageWidget(), - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_start'), - onPressed: () => GameUtils.startNewGame(myProvider), - ), - ], - ), - Parameters.buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/parameters.dart b/lib/ui/widgets/parameters.dart new file mode 100644 index 0000000000000000000000000000000000000000..11b080d42b81d7c750212791a67537e8979818ba --- /dev/null +++ b/lib/ui/widgets/parameters.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/config/default_game_settings.dart'; +import 'package:solitaire/config/default_global_settings.dart'; +import 'package:solitaire/cubit/settings_game_cubit.dart'; +import 'package:solitaire/cubit/settings_global_cubit.dart'; +import 'package:solitaire/ui/painters/parameter_painter.dart'; +import 'package:solitaire/ui/widgets/button_game_start_new.dart'; + +class Parameters extends StatelessWidget { + const Parameters({super.key}); + + final double separatorHeight = 8.0; + + @override + Widget build(BuildContext context) { + final List<Widget> lines = []; + + // Game settings + for (String code in DefaultGameSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: false, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + lines.add(SizedBox(height: separatorHeight)); + lines.add(const Expanded(child: StartNewGameButton())); + lines.add(SizedBox(height: separatorHeight)); + + // Global settings + for (String code in DefaultGlobalSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: true, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + return Column( + children: lines, + ); + } + + List<Widget> buildParametersLine({ + required String code, + required bool isGlobal, + }) { + final List<Widget> parameterButtons = []; + + final List<String> availableValues = isGlobal + ? DefaultGlobalSettings.getAvailableValues(code) + : DefaultGameSettings.getAvailableValues(code); + + if (availableValues.length <= 1) { + return []; + } + + for (String value in availableValues) { + final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameSettingsCubit gameSettingsCubit = + BlocProvider.of<GameSettingsCubit>(context); + final GlobalSettingsCubit globalSettingsCubit = + BlocProvider.of<GlobalSettingsCubit>(context); + + final String currentValue = isGlobal + ? globalSettingsCubit.getParameterValue(code) + : gameSettingsCubit.getParameterValue(code); + + final bool isActive = (value == currentValue); + + final double displayWidth = MediaQuery.of(context).size.width; + final double itemWidth = displayWidth / availableValues.length - 26; + + return TextButton( + child: CustomPaint( + size: Size(itemWidth, itemWidth), + willChange: false, + painter: ParameterPainter( + code: code, + value: value, + isSelected: isActive, + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + isComplex: true, + ), + onPressed: () => isGlobal + ? globalSettingsCubit.setParameterValue(code, value) + : gameSettingsCubit.setParameterValue(code, value), + ); + }, + ); + }, + ); + + parameterButtons.add(parameterButton); + } + + return parameterButtons; + } +} diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart deleted file mode 100644 index 349b05dd4e48f223109bb7647591e9bcbb27a12b..0000000000000000000000000000000000000000 --- a/lib/utils/board_utils.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'dart:math'; - -import 'package:solitaire/entities/tile.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/utils/tools.dart'; - -class BoardUtils { - static printGrid(List cells) { - String textBoard = ' '; - String textHole = '·'; - String textPeg = 'o'; - - printlog(''); - printlog('-------'); - for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { - String row = ''; - for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { - String textCell = textBoard; - Tile? tile = cells[rowIndex][colIndex]; - if (tile != null) { - textCell = tile.hasPeg ? textPeg : textHole; - } - row += textCell; - } - printlog(row); - } - printlog('-------'); - printlog(''); - } - - static Board createBoardFromSavedState(Data myProvider, String savedBoard) { - Board board = []; - int boardSize = pow((savedBoard.length), 1 / 2).round(); - myProvider.updateBoardSize(boardSize); - - String textBoard = ' '; - String textPeg = 'o'; - - int index = 0; - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - List<Tile?> row = []; - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - String stringValue = savedBoard[index++]; - if (stringValue == textBoard) { - row.add(null); - } else { - row.add(Tile(rowIndex, colIndex, (stringValue == textPeg))); - } - } - board.add(row); - } - - return board; - } - - static createNewBoard(Data myProvider) { - Map<String, List<String>> templates = { - 'french': [ - ' ooo ', - ' ooooo ', - 'ooo·ooo', - 'ooooooo', - 'ooooooo', - ' ooooo ', - ' ooo ', - ], - 'german': [ - ' ooo ', - ' ooo ', - ' ooo ', - 'ooooooooo', - 'oooo·oooo', - 'ooooooooo', - ' ooo ', - ' ooo ', - ' ooo ', - ], - 'english': [ - ' ooo ', - ' ooo ', - 'ooooooo', - 'ooo·ooo', - 'ooooooo', - ' ooo ', - ' ooo ', - ], - 'diamond': [ - ' o ', - ' ooo ', - ' ooooo ', - ' ooooooo ', - 'oooo·oooo', - ' ooooooo ', - ' ooooo ', - ' ooo ', - ' o ', - ] - }; - - List<String>? template = templates[myProvider.parameterLayout]; - - Board grid = []; - int row = 0; - template?.forEach((String line) { - List<Tile?> gridLine = []; - int col = 0; - line.split("").forEach((String tileCode) { - gridLine.add(tileCode == ' ' ? null : Tile(row, col, (tileCode == 'o'))); - col++; - }); - row++; - grid.add(gridLine); - }); - - printGrid(grid); - - myProvider.resetGame(); - myProvider.updateBoard(grid); - } -} diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart deleted file mode 100644 index 69b0f4f014abb036969d43e86a62c94f405540e3..0000000000000000000000000000000000000000 --- a/lib/utils/game_utils.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'package:solitaire/entities/tile.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/utils/board_utils.dart'; -import 'package:solitaire/utils/tools.dart'; - -class GameUtils { - static Future<void> quitGame(Data myProvider) async { - myProvider.updateGameIsRunning(false); - } - - static Future<void> quitAndDeleteCurrentGame(Data myProvider) async { - quitGame(myProvider); - myProvider.resetCurrentSavedState(); - } - - static Future<void> startNewGame(Data myProvider) async { - printlog('Starting game'); - - 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('layout', savedState['layout']); - myProvider.setParameterValue('skin', savedState['skin']); - myProvider.updateMovesCount(int.parse(savedState['movesCount'])); - myProvider.updateBoard( - BoardUtils.createBoardFromSavedState(myProvider, savedState['boardValues'])); - - myProvider.updateGameIsRunning(true); - } catch (e) { - printlog('Failed to resume game. Will start new one instead.'); - myProvider.resetCurrentSavedState(); - myProvider.initParametersValues(); - startNewGame(myProvider); - } - } else { - myProvider.resetCurrentSavedState(); - myProvider.initParametersValues(); - startNewGame(myProvider); - } - } - - static bool isMoveAllowed(Data myProvider, List<int> source, List<int> target) { - // printlog('(test) Pick from ' + source.toString() + ' and drop on ' + target.toString()); - final Board board = myProvider.board; - final int sourceCol = source[0]; - final int sourceRow = source[1]; - final int targetCol = target[0]; - final int targetRow = target[1]; - - // ensure source and target are inside range - if (sourceRow < 0 || - sourceRow > (myProvider.boardSize - 1) || - sourceCol < 0 || - sourceCol > (myProvider.boardSize - 1)) { - // printlog('move forbidden: source is out of board'); - return false; - } - if (targetRow < 0 || - targetRow > (myProvider.boardSize - 1) || - targetCol < 0 || - targetCol > (myProvider.boardSize - 1)) { - // printlog('move forbidden: target is out of board'); - return false; - } - - // ensure source exists and has a peg - if (board[sourceRow][sourceCol] == null || board[sourceRow][sourceCol]?.hasPeg == false) { - // printlog('move forbidden: source peg does not exist'); - return false; - } - - // ensure target exists and is empty - if (board[targetRow][targetCol] == null || board[targetRow][targetCol]?.hasPeg == true) { - // printlog('move forbidden: target does not exist or already with a peg'); - return false; - } - - // ensure source and target are in the same line/column - if ((targetCol != sourceCol) && (targetRow != sourceRow)) { - // printlog('move forbidden: source and target are not in the same line or column'); - return false; - } - - // ensure source and target are separated by exactly one tile - if (((targetCol == sourceCol) && ((targetRow - sourceRow).abs() != 2)) || - ((targetRow == sourceRow) && ((targetCol - sourceCol).abs() != 2))) { - // printlog('move forbidden: source and target must be separated by exactly one tile'); - return false; - } - - // ensure middle tile exists and has a peg - final int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round(); - final int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round(); - if (board[middleRow][middleCol] == null || board[middleRow][middleCol]?.hasPeg == false) { - // printlog('move forbidden: tile between source and target does not contain a peg'); - return false; - } - - // ok, move is allowed - return true; - } - - static void move(Data myProvider, List<int> source, List<int> target) { - printlog('Move from $source to $target'); - final int sourceCol = source[0]; - final int sourceRow = source[1]; - final int targetCol = target[0]; - final int targetRow = target[1]; - - final int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round(); - final int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round(); - - // remove peg from source - myProvider.updatePegValue(sourceRow, sourceCol, false); - // put peg in target - myProvider.updatePegValue(targetRow, targetCol, true); - // remove peg from middle tile - myProvider.updatePegValue(middleRow, middleCol, false); - - // increment moves count - myProvider.incrementMovesCount(); - // update remaining pegs count - myProvider.updateRemainingPegsCount(GameUtils.countRemainingPegs(myProvider)); - // update allowed moves count - myProvider.updateAllowedMovesCount(GameUtils.countAllowedMoves(myProvider)); - } - - static List<Tile> listRemainingPegs(Data myProvider) { - List<Tile> pegs = []; - - Board board = myProvider.board; - for (int rowIndex = 0; rowIndex < board.length; rowIndex++) { - for (int colIndex = 0; colIndex < board[rowIndex].length; colIndex++) { - Tile? tile = board[rowIndex][colIndex]; - if (tile != null && tile.hasPeg == true) { - pegs.add(tile); - } - } - } - - return pegs; - } - - static int countRemainingPegs(Data myProvider) { - return GameUtils.listRemainingPegs(myProvider).length; - } - - static int countAllowedMoves(Data myProvider) { - int allowedMovesCount = 0; - List<Tile> pegs = GameUtils.listRemainingPegs(myProvider); - for (Tile tile in pegs) { - final int row = tile.currentRow; - final int col = tile.currentCol; - final List<int> source = [col, row]; - final List<List<int>> targets = [ - [col - 2, row], - [col + 2, row], - [col, row - 2], - [col, row + 2], - ]; - for (List<int> target in targets) { - if (GameUtils.isMoveAllowed(myProvider, source, target)) { - allowedMovesCount++; - } - } - } - - return allowedMovesCount; - } -} diff --git a/pubspec.lock b/pubspec.lock index 12d429bb18ac218313d455ba71028cdbb3234bb9..bdba0ef12aaa5e28404023f4cff3d604ec53b942 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: bloc - sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.1.4" characters: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c + sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.7" easy_logger: dependency: transitive description: @@ -106,18 +106,18 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.1.5" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" flutter_localizations: dependency: transitive description: flutter @@ -140,10 +140,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_parser: dependency: transitive description: @@ -156,10 +156,10 @@ packages: dependency: "direct main" description: name: hydrated_bloc - sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d" + sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.1.5" intl: dependency: transitive description: @@ -172,10 +172,10 @@ packages: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" material_color_utilities: dependency: transitive description: @@ -200,32 +200,24 @@ 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" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "8.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" path: - dependency: "direct main" + dependency: transitive description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -236,26 +228,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -297,7 +289,7 @@ packages: source: hosted version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c @@ -305,29 +297,29 @@ packages: source: hosted version: "6.1.2" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -348,10 +340,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -425,18 +417,18 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1" win32: dependency: transitive description: name: win32 - sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.5.0" xdg_directories: dependency: transitive description: @@ -447,4 +439,4 @@ packages: version: "1.0.4" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.16.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5c4cd59b3a02df9bff6342803c1f8201562af795..5c638793da858ad6a393a3f36a889d1a87ab3897 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,12 @@ name: solitaire description: Solitaire Game -publish_to: 'none' -version: 0.0.17+17 +publish_to: "none" + +version: 0.0.18+18 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: @@ -16,16 +17,12 @@ dependencies: flutter_bloc: ^8.1.1 hive: ^2.2.3 hydrated_bloc: ^9.0.0 - overlay_support: ^2.1.0 - provider: ^6.0.5 - shared_preferences: ^2.2.1 - package_info_plus: ^5.0.1 - path: ^1.9.0 + package_info_plus: ^8.0.0 path_provider: ^2.0.11 unicons: ^2.1.1 dev_dependencies: - flutter_lints: ^3.0.1 + flutter_lints: ^4.0.0 flutter: uses-material-design: true @@ -45,4 +42,3 @@ flutter: weight: 400 - asset: assets/fonts/Nunito-Light.ttf weight: 300 -