diff --git a/android/gradle.properties b/android/gradle.properties index 3b4960c1971a8b84a921046012e24eefff6ad4e7..5b52231bd50fd20f2ed25aab85f3ba90e51a05e8 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.1.19 -app.versionCode=40 +app.versionName=0.2.0 +app.versionCode=41 diff --git a/assets/icons/button_start.png b/assets/icons/button_start.png deleted file mode 100644 index f0ead9744e59874fa15d70d7e5e49336a15009dd..0000000000000000000000000000000000000000 Binary files a/assets/icons/button_start.png and /dev/null differ diff --git a/assets/icons/level_easy.png b/assets/icons/level_easy.png deleted file mode 100644 index 9d3456d576604e1ac79281132bc958851eb7ee13..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_easy.png and /dev/null differ diff --git a/assets/icons/level_hard.png b/assets/icons/level_hard.png deleted file mode 100644 index c0205b11bf6ab02f8c18542cd395c44dbead210b..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_hard.png and /dev/null differ diff --git a/assets/icons/level_medium.png b/assets/icons/level_medium.png deleted file mode 100644 index a1ab48924779cbe1c30ff0fde110b6be827720ed..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_medium.png and /dev/null differ diff --git a/assets/icons/level_nightmare.png b/assets/icons/level_nightmare.png deleted file mode 100644 index 2077c8358db7ce531b3634f3a40e96c2032b4851..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_nightmare.png and /dev/null differ diff --git a/assets/icons/size_10x10.png b/assets/icons/size_10x10.png deleted file mode 100644 index d374f1bc3acf0dafd1d6e03c7a0af4fab71aea89..0000000000000000000000000000000000000000 Binary files a/assets/icons/size_10x10.png and /dev/null differ diff --git a/assets/icons/size_15x15.png b/assets/icons/size_15x15.png deleted file mode 100644 index b18f855d5b116dbc06f9fba1298ba84db538b1b5..0000000000000000000000000000000000000000 Binary files a/assets/icons/size_15x15.png and /dev/null differ diff --git a/assets/icons/size_20x20.png b/assets/icons/size_20x20.png deleted file mode 100644 index 547ba1ec72fc2175ef32d91f99587f4201ad9840..0000000000000000000000000000000000000000 Binary files a/assets/icons/size_20x20.png and /dev/null differ diff --git a/assets/icons/skin_default.png b/assets/icons/skin_default.png deleted file mode 100644 index 43dc59805c9f2d9d171b9e54bdccb8c82728d18b..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 e1e319186b59acfb7adcf7d8fcbb0b37dfe7f21c..0348cd0a3fe70f153cf9062cf63c55803b5baba3 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,16 +1,12 @@ { "app_name": "Minehunter", - "long_press_to_quit": "Long press to quit game...", - - "bottom_nav_home": "Game", - "bottom_nav_settings": "Settings", - "bottom_nav_about": "About", - "settings_title": "Settings", "settings_label_theme": "Theme mode", "about_title": "About", "about_content": "Minehunter.", - "about_version": "Version: {version}" + "about_version": "Version: {version}", + + "": "" } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 9243b745c43ec3c9f03af8f7127d356dd7a3fa85..08923674f984262ce593f327dce8da7ef22ab79f 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,16 +1,12 @@ { "app_name": "Démineur", - "long_press_to_quit": "Appuyer longtemps pour quitter le jeu...", - - "bottom_nav_home": "Jeu", - "bottom_nav_settings": "Réglages", - "bottom_nav_about": "Infos", - "settings_title": "Réglages", "settings_label_theme": "Thème de couleurs", "about_title": "Informations", "about_content": "Démineur.", - "about_version": "Version : {version}" + "about_version": "Version : {version}", + + "": "" } diff --git a/assets/icons/button_back.png b/assets/ui/button_back.png similarity index 100% rename from assets/icons/button_back.png rename to assets/ui/button_back.png diff --git a/assets/icons/button_delete_saved_game.png b/assets/ui/button_delete_saved_game.png similarity index 100% rename from assets/icons/button_delete_saved_game.png rename to assets/ui/button_delete_saved_game.png diff --git a/assets/icons/button_resume_game.png b/assets/ui/button_resume_game.png similarity index 100% rename from assets/icons/button_resume_game.png rename to assets/ui/button_resume_game.png diff --git a/assets/ui/button_start.png b/assets/ui/button_start.png new file mode 100644 index 0000000000000000000000000000000000000000..6845e2f5c21598ab61f1684d2075aeec0334bf23 Binary files /dev/null and b/assets/ui/button_start.png differ diff --git a/assets/icons/game_fail.png b/assets/ui/game_fail.png similarity index 100% rename from assets/icons/game_fail.png rename to assets/ui/game_fail.png diff --git a/assets/icons/game_win.png b/assets/ui/game_win.png similarity index 100% rename from assets/icons/game_win.png rename to assets/ui/game_win.png diff --git a/assets/icons/placeholder.png b/assets/ui/placeholder.png similarity index 100% rename from assets/icons/placeholder.png rename to assets/ui/placeholder.png diff --git a/fastlane/metadata/android/en-US/changelogs/41.txt b/fastlane/metadata/android/en-US/changelogs/41.txt new file mode 100644 index 0000000000000000000000000000000000000000..d4afd512e55b3fd8ffbfd795adb9b00832e5aaef --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/41.txt @@ -0,0 +1 @@ +Improve/normalize game architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/41.txt b/fastlane/metadata/android/fr-FR/changelogs/41.txt new file mode 100644 index 0000000000000000000000000000000000000000..6a9871a5eb8eb3c6e9106520f1cbf1f39f9e5ef7 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/41.txt @@ -0,0 +1 @@ +Amélioration/normalisation de l'architecture du jeu. diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh deleted file mode 100755 index 150bc83cd966790990f2d668f43b809b375aad59..0000000000000000000000000000000000000000 --- a/icons/build_game_icons.sh +++ /dev/null @@ -1,159 +0,0 @@ -#! /bin/bash - -# Check dependencies -command -v inkscape >/dev/null 2>&1 || { echo >&2 "I require inkscape but it's not installed. Aborting."; exit 1; } -command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not installed. Aborting."; exit 1; } -command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; } - -CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -BASE_DIR="$(dirname "${CURRENT_DIR}")" -ASSETS_DIR="${BASE_DIR}/assets" - -OPTIPNG_OPTIONS="-preserve -quiet -o7" -ICON_SIZE=192 - -####################################################### - -# Game images -AVAILABLE_GAME_IMAGES=" - button_back - button_start - button_resume_game - button_delete_saved_game - game_fail - game_win - placeholder -" - -# Settings images -AVAILABLES_GAME_SETTINGS=" - level:easy,medium,hard,nightmare - size:10x10,15x15,20x20 -" - -# Skins -AVAILABLE_SKINS=" - default -" - -# Images per skin -SKIN_IMAGES=" - empty - tile_0 - tile_1 - tile_2 - tile_3 - tile_4 - tile_5 - tile_6 - tile_7 - tile_8 - tile_flag_ko - tile_flag_ok - tile_flag - tile_mine_not_found - tile_mine - tile_unknown - button_mark_mine_on - button_mark_mine_off - indicator_report_on - indicator_report_off - indicator_walk_on - indicator_walk_off -" - -####################################################### - -# optimize svg -function optimize_svg() { - SOURCE="$1" - - cp ${SOURCE} ${SOURCE}.tmp - scour \ - --remove-descriptive-elements \ - --enable-id-stripping \ - --enable-viewboxing \ - --enable-comment-stripping \ - --nindent=4 \ - --quiet \ - -i ${SOURCE}.tmp \ - -o ${SOURCE} - rm ${SOURCE}.tmp -} - -# build icons -function build_icon() { - SOURCE="$1" - TARGET="$2" - - echo "Building ${TARGET}" - - if [ ! -f "${SOURCE}" ]; then - echo "Missing file: ${SOURCE}" - exit 1 - fi - - optimize_svg "${SOURCE}" - - inkscape \ - --export-width=${ICON_SIZE} \ - --export-height=${ICON_SIZE} \ - --export-filename=${TARGET} \ - ${SOURCE} - - 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 - build_icon ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_IMAGE}.svg ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_IMAGE}.png - done -} - -####################################################### - -# Create output folders -mkdir -p ${ASSETS_DIR}/icons -mkdir -p ${ASSETS_DIR}/skins - -# Delete existing generated images -find ${ASSETS_DIR}/icons -type f -name "*.png" -delete -find ${ASSETS_DIR}/skins -type f -name "*.png" -delete - -# build game images -for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES} -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 - build_icon_for_skin "${SKIN}" -done diff --git a/icons/button_start.svg b/icons/button_start.svg deleted file mode 100644 index 633a63410502bc516703555911588df2143957f1..0000000000000000000000000000000000000000 --- a/icons/button_start.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"/><g transform="matrix(.8268 0 0 .8268 9.0269 8.3829)" fill="#fefeff" stroke-linecap="round" stroke-linejoin="round"><path d="m34.852 25.44c-1.1248-1.1302-4.0012-1.1302-4.0012 0v45.921c0 1.1316 2.8832 1.1316 4.0121 0l37.693-20.918c1.1289-1.1248 1.1479-2.9551 0.02171-4.084z" stroke="#105ca1" stroke-width="8.257"/><path d="m36.382 28.754c-0.96243-0.96706-3.4236-0.96706-3.4236 0v39.292c0 0.96825 2.467 0.96825 3.4329 0l32.252-17.898c0.96594-0.96243 0.9822-2.5285 0.01858-3.4945z" stroke="#feffff" stroke-width="4.314"/></g></svg> diff --git a/icons/level_easy.svg b/icons/level_easy.svg deleted file mode 100644 index 3702968ed134ed5aaecbba1550056ce2ddeeb268..0000000000000000000000000000000000000000 --- a/icons/level_easy.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#97c05c" stroke="#000" stroke-width="2"/><g transform="matrix(.47655 0 0 .47655 27.173 27.173)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g></svg> diff --git a/icons/level_hard.svg b/icons/level_hard.svg deleted file mode 100644 index 9b592568b6ddbf92ecc017fb4cb315d8e1721e8a..0000000000000000000000000000000000000000 --- a/icons/level_hard.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#cd5542" stroke="#000" stroke-width="2"/><g transform="matrix(.47655 0 0 .47655 4.2858 6.7324)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g><g transform="matrix(.47655 0 0 .47655 50.06 6.7324)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g><g transform="matrix(.47655 0 0 .47655 27.173 47.615)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g></svg> diff --git a/icons/level_medium.svg b/icons/level_medium.svg deleted file mode 100644 index 34401cc3d6b6509d6aa5f0792549763014d5d1d3..0000000000000000000000000000000000000000 --- a/icons/level_medium.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#f29c38" stroke="#000" stroke-width="2"/><g transform="matrix(.47655 0 0 .47655 3.9408 27.173)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g><g transform="matrix(.47655 0 0 .47655 50.404 27.173)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g></svg> diff --git a/icons/level_nightmare.svg b/icons/level_nightmare.svg deleted file mode 100644 index 491ec918aae53c8ea05f2e3f6048e3ea90082ea5..0000000000000000000000000000000000000000 --- a/icons/level_nightmare.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6041b0" stroke="#000" stroke-width="2"/><g transform="matrix(.47655 0 0 .47655 5.1498 6.5734)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g><g transform="matrix(.47655 0 0 .47655 49.346 6.6426)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g><g transform="matrix(.47655 0 0 .47655 4.9988 47.703)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g><g transform="matrix(.47655 0 0 .47655 49.325 47.772)" fill="#fff" stroke="#000" stroke-width="4.1968"><path d="m92.148 55.869c-0.626-0.939-1.482-1.519-2.568-1.739l-6.792-1.242c0.368-5.669-0.727-11.035-3.286-16.096l5.853-4.224c0.939-0.663 1.491-1.574 1.657-2.733 0.202-1.086-0.037-2.071-0.718-2.954l-0.469-0.718c-0.626-0.883-1.537-1.427-2.733-1.629-1.031-0.166-2.016 0.092-2.954 0.773l-5.632 4.059c-3.81-4.215-8.347-7.206-13.611-8.973l1.27-7.731c0.202-1.104-0.037-2.126-0.718-3.065-0.626-0.883-1.482-1.427-2.568-1.629l-0.856-0.166c-1.104-0.147-2.126 0.092-3.065 0.718-0.884 0.681-1.427 1.565-1.629 2.65l-1.16 7.427c-2.503-0.221-4.951-0.175-7.344 0.138-1.362 0.055-2.641 0.294-3.838 0.718-0.626 0.147-1.224 0.331-1.795 0.552l-3.451-7.51c-0.46-0.994-1.215-1.693-2.264-2.098-1.031-0.423-2.043-0.405-3.037 0.055l-0.773 0.414c-0.994 0.405-1.73 1.16-2.209 2.264-0.368 1.031-0.313 2.043 0.166 3.037l3.506 7.648c-0.202 0.11-0.405 0.249-0.607 0.414-5.264 3.681-9.019 8.31-11.265 13.888l-6.958-1.546c-1.086-0.276-2.098-0.074-3.037 0.607-0.994 0.534-1.592 1.344-1.795 2.43l-0.246 0.772c-0.203 1.104-0.018 2.172 0.552 3.203 0.626 0.883 1.491 1.463 2.595 1.739l6.626 1.546c-0.736 5.835 0.101 11.329 2.512 16.483l-5.411 3.672c-0.939 0.626-1.537 1.491-1.795 2.595-0.147 1.086 0.092 2.098 0.718 3.037l0.469 0.635c0.626 0.939 1.454 1.537 2.485 1.795 1.16 0.147 2.209-0.092 3.147-0.718l5.135-3.424c3.7 4.583 8.255 7.887 13.667 9.912l-1.242 6.323c-0.202 1.104 9e-3 2.154 0.635 3.147 0.626 0.883 1.482 1.427 2.568 1.629l0.801 0.166c1.086 0.258 2.098 0.101 3.037-0.469 0.939-0.681 1.509-1.574 1.712-2.678l1.242-6.074c6 0.626 11.651-0.285 16.952-2.733l4.003 6.074c0.626 0.939 1.509 1.518 2.651 1.739 1.049 0.258 2.043 0.074 2.982-0.552l0.69-0.387c0.939-0.626 1.537-1.518 1.795-2.678 0.203-1.086-9e-3 -2.098-0.635-3.037l-3.81-5.936c4.583-3.7 7.841-8.255 9.774-13.667l6.875 1.242c1.086 0.202 2.098-9e-3 3.037-0.635 0.883-0.607 1.427-1.463 1.629-2.568l0.166-0.773c0.202-1.103-0.01-2.143-0.636-3.119z" fill="#fff" stroke="#000" stroke-width="4.1968"/></g></svg> diff --git a/icons/size_10x10.svg b/icons/size_10x10.svg deleted file mode 100644 index 7729c4bed977f04ce8965362de2b7f63156a54ed..0000000000000000000000000000000000000000 --- a/icons/size_10x10.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#97c05c" stroke="#000" stroke-width="2"/> - <g transform="matrix(.61667 0 0 .61667 19.166 8.0878)"><rect x="27.579" y="45.549" width="43.928" height="43.924" fill="#fff"/><path d="m22.09 40.056v55.821h55.821v-55.821zm49.417 49.417h-19.218v-19.218h19.218zm-0.0022-24.706h-19.217v-19.218h19.218zm-43.926-19.218h19.218v19.218h-19.218zm0 24.71h19.218v19.218h-19.218z" stroke-width="2.7453"/></g></svg> diff --git a/icons/size_15x15.svg b/icons/size_15x15.svg deleted file mode 100644 index 8f03f28c93750af237f770416425f8b4a795d98d..0000000000000000000000000000000000000000 --- a/icons/size_15x15.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#f29c38" stroke="#000" stroke-width="2"/> - <g transform="translate(0 -19.83)"><rect x="25.631" y="45.406" width="48.427" height="48.427" fill="#fefdfd"/><path d="m21.909 41.68v56.301h56.182v-56.301zm35.388 20.489v14.901h-14.901v-14.901zm-14.901-3.7252v-13.038h14.901v13.038zm31.662 35.389h-13.038v-13.038h13.038zm0-16.762h-13.038v-14.901h13.038zm-16.762 3.7252v13.038h-14.901v-13.038zm16.762-22.352h-13.038v-13.038h13.038zm-48.427-13.038h13.038v13.038h-13.038zm0 16.762h13.038v14.901h-13.038zm0 18.627h13.038v13.038h-13.038z" stroke-width="1.8627"/></g></svg> diff --git a/icons/size_20x20.svg b/icons/size_20x20.svg deleted file mode 100644 index 591d8628449c45eb6103a10e519fafc2fa224dfb..0000000000000000000000000000000000000000 --- a/icons/size_20x20.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#cd5542" stroke="#000" stroke-width="2"/> - <g transform="matrix(1.2319 0 0 1.2319 -11.595 -34.78)"><rect x="25.882" y="43.926" width="48" height="49.184" fill="#fff"/><path d="m23.09 41.131v55.377h53.819v-55.377zm25.359 15.368v11.176h-9.9925v-11.176zm-9.9925-2.794v-9.7789h9.9925v9.7789zm35.426 39.405h-9.7789v-9.7789h9.7789zm0-12.862h-9.7789v-9.7789h9.7789zm-12.862 0h-9.7789v-9.7789h9.7789zm0 3.0824v9.7789h-9.7789v-9.7789zm12.862-15.655h-9.7789v-11.176h9.7789zm-12.862 0h-9.7789v-11.176h9.7789zm-12.572 2.794v9.7789h-9.9925v-9.7789zm0 12.862v9.7789h-9.9925v-9.7789zm25.434-39.405v9.7789h-9.7789v-9.7789zm-12.862 9.7789h-9.7789v-9.7789h9.7789zm-35.138-9.7789h9.7789v9.7789h-9.7789zm0 12.572h9.7789v11.176h-9.7789zm0 13.971h9.7789v9.7789h-9.7789zm0 12.862h9.7789v9.7789h-9.7789z" stroke-width="1.3971"/></g></svg> diff --git a/icons/skin_default.svg b/icons/skin_default.svg deleted file mode 100644 index bb34f915009a3b22734bec8c85090bcb036d3e63..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="#a4a4a4" stroke="#000" stroke-width="2"/><rect x="-15.387" y="-20.515" width="100" height="100" ry="2" fill="none"/></svg> diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..b1784bcd2b39f5c7567a5b4a79134cb5cb60e169 --- /dev/null +++ b/lib/config/default_game_settings.dart @@ -0,0 +1,63 @@ +import 'package:minehunter/utils/tools.dart'; + +class DefaultGameSettings { + // available game parameters codes + static const String parameterCodeLevel = 'level'; + static const String parameterCodeSize = 'size'; + static const List<String> availableParameters = [ + parameterCodeLevel, + parameterCodeSize, + ]; + + // level: available values + static const String levelValueEasy = 'easy'; + static const String levelValueMedium = 'medium'; + static const String levelValueHard = 'hard'; + static const String levelValueNightmare = 'nightmare'; + static const List<String> allowedLevelValues = [ + levelValueEasy, + levelValueMedium, + levelValueHard, + levelValueNightmare, + ]; + // level: default value + static const String defaultLevelValue = levelValueMedium; + + // size: available values + static const String sizeValueSmall = '10x10'; + static const String sizeValueMedium = '15x15'; + static const String sizeValueLarge = '20x20'; + static const List<String> allowedSizeValues = [ + sizeValueSmall, + sizeValueMedium, + sizeValueLarge, + ]; + // size: default value + static const String defaultSizeValue = sizeValueMedium; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeLevel: + return DefaultGameSettings.allowedLevelValues; + case parameterCodeSize: + return DefaultGameSettings.allowedSizeValues; + } + + printlog('Did not find any available value for game parameter "$parameterCode".'); + return []; + } + + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + // + ]; + + // how many mines? (per 100 tiles) + static const Map<String, int> minesCountRatios = { + DefaultGameSettings.levelValueEasy: 5, + DefaultGameSettings.levelValueMedium: 10, + DefaultGameSettings.levelValueHard: 15, + DefaultGameSettings.levelValueNightmare: 20, + }; +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..3742f45ff113289a66e6015195b10fcaa4246834 --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,33 @@ +import 'package:minehunter/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; + + // available values from parameter code + 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 []; + } + + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + // + ]; +} diff --git a/lib/config/menu.dart b/lib/config/menu.dart index 09df83be163fa0d2f965c982b46749e27060e8fe..3cac643a13f9d34db64aafb36e0bf43643e737cf 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -1,53 +1,51 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; - -import 'package:minehunter/ui/screens/about_page.dart'; -import 'package:minehunter/ui/screens/game_page.dart'; -import 'package:minehunter/ui/screens/settings_page.dart'; import 'package:unicons/unicons.dart'; +import 'package:minehunter/ui/screens/page_about.dart'; +import 'package:minehunter/ui/screens/page_game.dart'; +import 'package:minehunter/ui/screens/page_settings.dart'; + class MenuItem { - final String code; final Icon icon; final Widget page; const MenuItem({ - required this.code, required this.icon, required this.page, }); } 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( + 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( + icon: Icon(UniconsLine.setting), + page: PageSettings(), + ); + + static const indexAbout = 2; + static const menuItemAbout = MenuItem( + 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/config/theme.dart b/lib/config/theme.dart index 138460e58f89bc93afb0899d6cfe8f8e662a466f..74f532fd5abf693979118609564d29167e902009 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -188,5 +188,3 @@ final ThemeData darkTheme = lightTheme.copyWith( ), ), ); - -final ThemeData appTheme = darkTheme; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..46715d8ff4f76fcb31ddc404bad3d4f0d9863e32 --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,272 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:minehunter/models/game/cell.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/models/settings/settings_game.dart'; +import 'package:minehunter/models/settings/settings_global.dart'; +import 'package:minehunter/models/types.dart'; +import 'package:minehunter/utils/tools.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() + : super(GameState( + currentGame: Game.createEmpty(), + )); + + void updateState(Game game) { + emit(GameState( + currentGame: game, + )); + } + + void refresh() { + final Game game = Game( + // Settings + gameSettings: state.currentGame.gameSettings, + globalSettings: state.currentGame.globalSettings, + // State + isRunning: state.currentGame.isRunning, + isStarted: state.currentGame.isStarted, + isFinished: state.currentGame.isFinished, + animationInProgress: state.currentGame.animationInProgress, + // Base data + board: state.currentGame.board, + sizeHorizontal: state.currentGame.sizeHorizontal, + sizeVertical: state.currentGame.sizeVertical, + isBoardMined: state.currentGame.isBoardMined, + // Game data + minesCount: state.currentGame.minesCount, + reportMode: state.currentGame.reportMode, + gameWin: state.currentGame.gameWin, + gameFail: state.currentGame.gameFail, + ); + // game.dump(); + + updateState(game); + } + + void startNewGame({ + required GameSettings gameSettings, + required GlobalSettings globalSettings, + }) { + final Game newGame = Game.createNew( + // Settings + gameSettings: gameSettings, + globalSettings: globalSettings, + ); + + newGame.dump(); + + updateState(newGame); + refresh(); + } + + void quitGame() { + state.currentGame.isRunning = false; + refresh(); + } + + void resumeSavedGame() { + state.currentGame.isRunning = true; + refresh(); + } + + void deleteSavedGame() { + state.currentGame.isRunning = false; + state.currentGame.isFinished = true; + refresh(); + } + + void updateBoard(Board board) { + state.currentGame.board = board; + refresh(); + } + + void addMines({ + required int forbiddenRow, + required int forbiddenCol, + }) { + state.currentGame.isStarted = true; + state.currentGame.addMines( + forbiddenRow: forbiddenRow, + forbiddenCol: forbiddenCol, + ); + state.currentGame.isBoardMined = true; + refresh(); + } + + void reportCell({ + required int row, + required int col, + }) { + if (!state.currentGame.board[row][col].isExplored) { + toggleCellMark(row, col); + } + } + + void walkOnCell({ + required int row, + required int col, + }) { + setCellAsExplored(row, col); + + if (state.currentGame.board[row][col].minesCountAround == 0) { + final List<List<int>> safeCells = getAllSafeCellsAround(row, col); + for (int safeCellIndex = 0; safeCellIndex < safeCells.length; safeCellIndex++) { + final int safeCellRow = safeCells[safeCellIndex][0]; + final int safeCellCol = safeCells[safeCellIndex][1]; + if (!state.currentGame.board[safeCellRow][safeCellCol].isExplored) { + walkOnCell( + row: safeCellRow, + col: safeCellCol, + ); + } + } + } + } + + List<List<int>> getAllSafeCellsAround(int row, int col) { + final List<List<Cell>> board = state.currentGame.board; + final int sizeHorizontal = board.length; + final int sizeVertical = board[0].length; + + final List<List<int>> safeCellsCoordinates = []; + + if (board[row][col].minesCountAround == 0) { + for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { + for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { + final int candidateRow = row + deltaRow; + final int candidateCol = col + deltaCol; + if ((candidateRow >= 0 && candidateRow < sizeVertical) && + (candidateCol >= 0 && candidateCol < sizeHorizontal) && + !board[candidateRow][candidateCol].isExplored) { + safeCellsCoordinates.add([candidateRow, candidateCol]); + } + } + } + } + + return safeCellsCoordinates; + } + + void setCellAsExplored(int row, int col) { + state.currentGame.board[row][col].isExplored = true; + state.currentGame.board[row][col].isMarked = false; + + // Boom? + if (state.currentGame.board[row][col].isMined) { + // Boom! + state.currentGame.board[row][col].isExploded = true; + } + + refresh(); + } + + void toggleCellMark(int row, int col) { + state.currentGame.board[row][col].isMarked = !state.currentGame.board[row][col].isMarked; + refresh(); + } + + void updateReportMode(bool reportMode) { + state.currentGame.reportMode = reportMode; + refresh(); + } + + bool checkGameIsFinished() { + final Game currentGame = state.currentGame; + + final Board board = currentGame.board; + final int sizeHorizontal = board.length; + final int sizeVertical = board[0].length; + + currentGame.printGrid(); + + updateGameWin(false); + updateGameFail(false); + + for (int row = 0; row < sizeVertical; row++) { + for (int col = 0; col < sizeHorizontal; col++) { + // Walked on a mine + if (board[row][col].isExploded == true) { + updateGameFail(true); + return true; + } + } + } + + for (int row = 0; row < sizeVertical; row++) { + for (int col = 0; col < sizeHorizontal; col++) { + if ( + // Mine not already found + (board[row][col].isMined == true && board[row][col].isMarked == false) || + // Safe cell marked as mined + (board[row][col].isMined == false && board[row][col].isMarked == true)) { + return false; + } + } + } + + printlog('-> ok all mines found!'); + updateGameWin(true); + + return true; + } + + void updateGameWin(bool value) { + state.currentGame.gameWin = value; + if (true == value) { + state.currentGame.isFinished = true; + } + refresh(); + } + + void updateGameFail(bool value) { + state.currentGame.gameFail = value; + if (true == value) { + state.currentGame.isFinished = true; + } + refresh(); + } + + void updateAnimationInProgress(bool animationInProgress) { + state.currentGame.animationInProgress = animationInProgress; + refresh(); + } + + void setAnimatedBackground(List animatedCellsPattern) { + for (int row = 0; row < state.currentGame.sizeVertical; row++) { + for (int col = 0; col < state.currentGame.sizeHorizontal; col++) { + state.currentGame.board[row][col].isAnimated = animatedCellsPattern[row][col]; + } + } + refresh(); + } + + void resetAnimatedBackground() { + for (int row = 0; row < state.currentGame.sizeVertical; row++) { + for (int col = 0; col < state.currentGame.sizeHorizontal; col++) { + state.currentGame.board[row][col].isAnimated = false; + } + } + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + final 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..00e211668c3269255926939324355792abd61c41 --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,15 @@ +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, + ]; +} 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 487ffb2870c6bdcc8df87afbe7059837f25fa2ca..e927ae1675fb1b80282d6cbac0bf2b236dc87f67 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:minehunter/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..fcb19a33e9e4e1869741e2a2d8f417a5806cb3ba --- /dev/null +++ b/lib/cubit/settings_game_cubit.dart @@ -0,0 +1,72 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:minehunter/config/default_game_settings.dart'; +import 'package:minehunter/models/settings/settings_game.dart'; + +part 'settings_game_state.dart'; + +class GameSettingsCubit extends HydratedCubit<GameSettingsState> { + GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault())); + + void setValues({ + String? itemsCount, + String? timerValue, + }) { + emit( + GameSettingsState( + settings: GameSettings( + level: itemsCount ?? state.settings.level, + size: timerValue ?? state.settings.size, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGameSettings.parameterCodeLevel: + return GameSettings.getLevelValueFromUnsafe(state.settings.level); + case DefaultGameSettings.parameterCodeSize: + return GameSettings.getSizeValueFromUnsafe(state.settings.size); + } + + return ''; + } + + void setParameterValue(String code, String value) { + final String itemsCount = code == DefaultGameSettings.parameterCodeLevel + ? value + : getParameterValue(DefaultGameSettings.parameterCodeLevel); + final String timerValue = code == DefaultGameSettings.parameterCodeSize + ? value + : getParameterValue(DefaultGameSettings.parameterCodeSize); + + setValues( + itemsCount: itemsCount, + timerValue: timerValue, + ); + } + + @override + GameSettingsState? fromJson(Map<String, dynamic> json) { + final String itemsCount = json[DefaultGameSettings.parameterCodeLevel] as String; + final String timerValue = json[DefaultGameSettings.parameterCodeSize] as String; + + return GameSettingsState( + settings: GameSettings( + level: itemsCount, + size: timerValue, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GameSettingsState state) { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeLevel: state.settings.level, + DefaultGameSettings.parameterCodeSize: state.settings.size, + }; + } +} diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..5acd85b44ba541e1c5e9c26af1c4be26a385b9ed --- /dev/null +++ b/lib/cubit/settings_game_state.dart @@ -0,0 +1,15 @@ +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, + ]; +} diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..66d6f6d6bd6c46639ef91293c6e16a385ff36ca2 --- /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:minehunter/config/default_global_settings.dart'; +import 'package:minehunter/models/settings/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..ebcddd700f252257223ca8e16c85202b04f3ff24 --- /dev/null +++ b/lib/cubit/settings_global_state.dart @@ -0,0 +1,15 @@ +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, + ]; +} diff --git a/lib/entities/cell.dart b/lib/entities/cell.dart deleted file mode 100644 index a70bd021e8aabb1e925c180f40e23930518992df..0000000000000000000000000000000000000000 --- a/lib/entities/cell.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/utils/board_animate.dart'; -import 'package:minehunter/utils/board_utils.dart'; - -class Cell { - bool isMined = false; - bool isExplored = false; - bool isMarked = false; - bool isExploded = false; - int minesCountAround = 0; - - bool isAnimated = false; - - Cell( - this.isMined, - ); - - /* - * Build widget for board cell, with interactions - */ - Container widget(Data myProvider, int row, int col) { - String imageAsset = getImageAssetName(myProvider); - - return Container( - decoration: BoxDecoration( - color: getBackgroundColor(myProvider), - border: getCellBorders(myProvider, row, col), - ), - child: GestureDetector( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 100), - transitionBuilder: (Widget child, Animation<double> animation) { - return ScaleTransition(scale: animation, child: child); - }, - child: Image( - image: AssetImage(imageAsset), - fit: BoxFit.fill, - key: ValueKey<int>(imageAsset.hashCode), - ), - ), - onTap: () { - // Set mines on board after first player try - if (!myProvider.isBoardMined) { - myProvider.updateBoard(BoardUtils.createBoard(myProvider, row, col)); - myProvider.updateIsBoardMined(true); - } - - if (!(myProvider.gameWin || myProvider.gameFail)) { - if (myProvider.reportMode) { - BoardUtils.reportCell(myProvider, row, col); - } else { - BoardUtils.walkOnCell(myProvider, row, col); - } - if (BoardUtils.checkGameIsFinished(myProvider)) { - myProvider.updateReportMode(false); - BoardAnimate.startAnimation(myProvider, myProvider.gameWin ? 'win' : 'fail'); - } - } - }, - ), - ); - } - - /* - * Compute image asset name, from skin and cell value/state - */ - String getImageAssetName(Data myProvider) { - String imageAsset = 'assets/skins/${myProvider.parameterSkin}_tile_unknown.png'; - - bool showSolution = myProvider.gameWin || myProvider.gameFail; - if (!showSolution) { - // Running game - if (isExplored) { - if (isMined) { - // Boom - imageAsset = 'assets/skins/${myProvider.parameterSkin}_tile_mine.png'; - } else { - // Show mines count around - imageAsset = 'assets/skins/${myProvider.parameterSkin}_tile_$minesCountAround.png'; - } - } else { - if (isMarked) { - // Danger! - imageAsset = 'assets/skins/${myProvider.parameterSkin}_tile_flag.png'; - } - } - } else { - // Finished game - if (isMined) { - if (isExploded) { - // Mine exploded - imageAsset = 'assets/skins/${myProvider.parameterSkin}_tile_mine.png'; - } else { - // Mine not found - imageAsset = 'assets/skins/${myProvider.parameterSkin}_tile_mine_not_found.png'; - } - } else { - // Show all mines counts - imageAsset = 'assets/skins/${myProvider.parameterSkin}_tile_$minesCountAround.png'; - } - } - - return imageAsset; - } - - // Compute cell background color, from cell state - Color getBackgroundColor(Data myProvider) { - if (myProvider.gameWin) { - return isAnimated ? Colors.green.shade400 : Colors.green.shade500; - } - if (myProvider.gameFail) { - return isAnimated ? Colors.pink.shade200 : Colors.pink.shade400; - } - - return isAnimated ? Colors.white : Colors.grey.shade200; - } - - // Compute cell borders, from board size and cell state - Border getCellBorders(Data myProvider, int row, int col) { - Color cellBorderColor = Colors.grey.shade500; - double cellBorderWidth = 4; - - // Reduce cell border width on big boards - int boardSize = myProvider.sizeHorizontal; - if (boardSize > 8) { - cellBorderWidth = 2; - if (boardSize > 10) { - cellBorderWidth = 1; - } - } - - if (myProvider.gameWin) { - cellBorderColor = Colors.green.shade700; - } else if (myProvider.gameFail) { - cellBorderColor = Colors.pink.shade300; - } - - Border borders = Border.all( - color: cellBorderColor, - width: cellBorderWidth, - ); - - return borders; - } -} diff --git a/lib/main.dart b/lib/main.dart index d1bb46add6222a3492b308b3ff35f6ff275d524e..2427470ae3d2ea20f3e02d80acaefe7e110abf45 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,20 +2,23 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hive/hive.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:provider/provider.dart'; +import 'package:minehunter/config/default_global_settings.dart'; import 'package:minehunter/config/theme.dart'; -import 'package:minehunter/cubit/bottom_nav_cubit.dart'; +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/cubit/nav_cubit.dart'; +import 'package:minehunter/cubit/settings_game_cubit.dart'; +import 'package:minehunter/cubit/settings_global_cubit.dart'; import 'package:minehunter/cubit/theme_cubit.dart'; -import 'package:minehunter/provider/data.dart'; import 'package:minehunter/ui/skeleton.dart'; void main() async { - /// Initialize packages + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); final Directory tmpDir = await getTemporaryDirectory(); @@ -24,18 +27,17 @@ void main() async { storageDirectory: tmpDir, ); - runApp( - EasyLocalization( - path: 'assets/translations', - supportedLocales: const <Locale>[ - Locale('en'), - Locale('fr'), - ], - fallbackLocale: const Locale('en'), - useFallbackTranslations: true, - child: const MyApp(), - ), - ); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) + .then((value) => runApp(EasyLocalization( + path: 'assets/translations', + supportedLocales: const <Locale>[ + Locale('en'), + Locale('fr'), + ], + fallbackLocale: const Locale('en'), + useFallbackTranslations: true, + child: const MyApp(), + ))); } class MyApp extends StatelessWidget { @@ -43,37 +45,83 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { + final List<String> assets = getImagesAssets(); + for (String asset in assets) { + precacheImage(AssetImage(asset), 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 MaterialApp( - title: 'Minehunter', - home: const SkeletonScreen(), + return MaterialApp( + title: 'Minehunter', + 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, ); }, ), ); } + + List<String> getImagesAssets() { + 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 image in gameImages) { + assets.add('assets/ui/$image.png'); + } + + final List<String> skinImages = [ + 'button_mark_mine_off', + 'button_mark_mine_on', + 'empty', + 'indicator_report_off', + 'indicator_report_on', + 'indicator_walk_off', + 'indicator_walk_on', + 'tile_flag_ko', + 'tile_flag_ok', + 'tile_flag', + 'tile_mine_not_found', + 'tile_mine', + 'tile_unknown', + ]; + for (int value = 0; value <= 8; value++) { + skinImages.add('tile_$value'); + } + + for (String skin in DefaultGlobalSettings.allowedSkinValues) { + for (String image in skinImages) { + assets.add('assets/skins/${skin}_$image.png'); + } + } + + return assets; + } } diff --git a/lib/models/game/cell.dart b/lib/models/game/cell.dart new file mode 100644 index 0000000000000000000000000000000000000000..e9b85b10757e0f14fe0d13f41744d3eb03b35343 --- /dev/null +++ b/lib/models/game/cell.dart @@ -0,0 +1,29 @@ +class Cell { + Cell( + this.isMined, + ); + + bool isMined = false; + bool isExplored = false; + bool isMarked = false; + bool isExploded = false; + int minesCountAround = 0; + + bool isAnimated = false; + + @override + String toString() { + return '$Cell(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'isMined': isMined, + 'isExplored': isExplored, + 'isMarked': isMarked, + 'isExploded': isExploded, + 'minesCountAround': minesCountAround, + 'isAnimated': isAnimated, + }; + } +} diff --git a/lib/models/game/game.dart b/lib/models/game/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..40056896d01620c84136a7bb1f5a20115185c03f --- /dev/null +++ b/lib/models/game/game.dart @@ -0,0 +1,267 @@ +import 'package:minehunter/config/default_game_settings.dart'; +import 'package:minehunter/models/game/cell.dart'; +import 'package:minehunter/models/settings/settings_game.dart'; +import 'package:minehunter/models/settings/settings_global.dart'; +import 'package:minehunter/models/types.dart'; +import 'package:minehunter/utils/tools.dart'; + +class Game { + Game({ + // Settings + required this.gameSettings, + required this.globalSettings, + + // State + this.isRunning = false, + this.isStarted = false, + this.isFinished = false, + this.animationInProgress = false, + + // Base data + required this.board, + required this.sizeHorizontal, + required this.sizeVertical, + this.isBoardMined = false, + + // Game data + this.minesCount = 0, + this.reportMode = false, + this.gameWin = false, + this.gameFail = false, + }); + + // Settings + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + // State + bool isRunning; + bool isStarted; + bool isFinished; + bool animationInProgress; + + // Base data + Board board; + final int sizeHorizontal; + final int sizeVertical; + bool isBoardMined; + + // Game data + int minesCount; + bool reportMode; + bool gameWin; + bool gameFail; + + factory Game.createEmpty() { + return Game( + // Settings + gameSettings: GameSettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), + // Base data + board: [], + sizeHorizontal: 0, + sizeVertical: 0, + ); + } + + factory Game.createNew({ + GameSettings? gameSettings, + GlobalSettings? globalSettings, + }) { + final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); + final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + final int sizeHorizontal = int.parse(newGameSettings.size.split('x')[0]); + final int sizeVertical = int.parse(newGameSettings.size.split('x')[1]); + + // Create empty board (it will be mined after first hit) + final Board board = []; + for (int rowIndex = 0; rowIndex < sizeVertical; rowIndex++) { + final List<Cell> row = []; + for (int colIndex = 0; colIndex < sizeHorizontal; colIndex++) { + row.add(Cell(false)); + } + board.add(row); + } + + final int minesCountRatio = + DefaultGameSettings.minesCountRatios[newGameSettings.level] ?? 1; + final int minesCount = ((sizeHorizontal * sizeVertical) * minesCountRatio / 100).round(); + printlog('Mines count: $minesCount'); + + return Game( + // Settings + gameSettings: newGameSettings, + globalSettings: newGlobalSettings, + // State + isRunning: true, + // Base data + board: board, + sizeHorizontal: sizeHorizontal, + sizeVertical: sizeVertical, + // Game data + minesCount: minesCount, + ); + } + + void addMines({ + required int forbiddenRow, + required int forbiddenCol, + }) { + // Shuffle cells to put random mines, except on currently selected one + final List<List<int>> allowedCells = []; + for (int row = 0; row < sizeVertical; row++) { + for (int col = 0; col < sizeHorizontal; col++) { + if (!((forbiddenRow == row) && (forbiddenCol == col))) { + allowedCells.add([row, col]); + } + } + } + allowedCells.shuffle(); + + // Put random mines on board + for (int mineIndex = 0; mineIndex < minesCount; mineIndex++) { + board[allowedCells[mineIndex][0]][allowedCells[mineIndex][1]].isMined = true; + } + + // Compute all mines counts on cells + for (int row = 0; row < sizeVertical; row++) { + for (int col = 0; col < sizeHorizontal; col++) { + board[row][col].minesCountAround = getMinesCountAround(row, col); + } + } + + printGrid(); + } + + int getMinesCountAround(int row, int col) { + final int sizeHorizontal = board.length; + final int sizeVertical = board[0].length; + + int minesCountAround = 0; + for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { + for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { + if ((row + deltaRow >= 0 && row + deltaRow < sizeVertical) && + (col + deltaCol >= 0 && col + deltaCol < sizeHorizontal) && + (board[row + deltaRow][col + deltaCol].isMined)) { + minesCountAround++; + } + } + } + + return minesCountAround; + } + + int countFlaggedCells() { + int count = 0; + + final int sizeHorizontal = board.length; + final int sizeVertical = board[0].length; + for (int row = 0; row < sizeVertical; row++) { + for (int col = 0; col < sizeHorizontal; col++) { + if (board[row][col].isMarked == true) { + count++; + } + } + } + + return count; + } + + bool get canBeResumed => isStarted && !isFinished; + bool get gameWon => isRunning && isStarted && isFinished; + + void dump() { + printlog(''); + printlog('## Current game dump:'); + printlog(''); + printlog('$Game:'); + printlog(' Settings'); + gameSettings.dump(); + globalSettings.dump(); + printlog(' State'); + printlog(' isRunning: $isRunning'); + printlog(' isStarted: $isStarted'); + printlog(' isFinished: $isFinished'); + printlog(' animationInProgress: $animationInProgress'); + printlog(' Base data'); + printGrid(); + printlog(' sizeHorizontal: $sizeHorizontal'); + printlog(' sizeVertical: $sizeVertical'); + printlog(' isBoardMined: $isBoardMined'); + printlog(' Game data'); + printlog(' minesCount: $minesCount'); + printlog(' reportMode: $reportMode'); + printlog(' gameWin: $gameWin'); + printlog(' gameFail: $gameFail'); + printlog(''); + } + + void printGrid() { + const String isMined = 'X'; + const String isSafe = '.'; + + const String mineFound = '#'; + const String wrongMarkedCell = '0'; + const String exploredSafeCell = '.'; + const String unkownState = ' '; + + printlog(''); + String line = '--'; + for (int i = 0; i < board[0].length; i++) { + line += '-'; + } + printlog('$line $line'); + for (int rowIndex = 0; rowIndex < board.length; rowIndex++) { + String currentLine = ''; + String solvedLine = ''; + for (int colIndex = 0; colIndex < board[rowIndex].length; colIndex++) { + solvedLine += board[rowIndex][colIndex].isMined ? isMined : isSafe; + + String cellString = unkownState; + if (board[rowIndex][colIndex].isExplored) { + cellString = exploredSafeCell; + } + if (board[rowIndex][colIndex].isMarked) { + if (board[rowIndex][colIndex].isMined) { + cellString = mineFound; + } else { + cellString = wrongMarkedCell; + } + } + currentLine += cellString; + } + printlog('|$currentLine| |$solvedLine|'); + } + printlog('$line $line'); + printlog(''); + } + + @override + String toString() { + return '$Game(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + // Settings + 'gameSettings': gameSettings.toJson(), + 'globalSettings': globalSettings.toJson(), + // State + 'isRunning': isRunning, + 'isStarted': isStarted, + 'isFinished': isFinished, + 'animationInProgress': animationInProgress, + // Base data + 'board': board, + 'sizeHorizontal': sizeHorizontal, + 'sizeVertical': sizeVertical, + 'isBoardMined': isBoardMined, + // Game data + 'minesCount': minesCount, + 'reportMode': reportMode, + 'gameWin': gameWin, + 'gameFail': gameFail, + }; + } +} diff --git a/lib/models/settings/settings_game.dart b/lib/models/settings/settings_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..f2b3b56b9b7d1fe970e7181bab85acfeb92cc7e9 --- /dev/null +++ b/lib/models/settings/settings_game.dart @@ -0,0 +1,54 @@ +import 'package:minehunter/config/default_game_settings.dart'; +import 'package:minehunter/utils/tools.dart'; + +class GameSettings { + final String level; + final String size; + + GameSettings({ + required this.level, + required this.size, + }); + + static String getLevelValueFromUnsafe(String level) { + if (DefaultGameSettings.allowedLevelValues.contains(level)) { + return level; + } + + return DefaultGameSettings.defaultLevelValue; + } + + static String getSizeValueFromUnsafe(String size) { + if (DefaultGameSettings.allowedSizeValues.contains(size)) { + return size; + } + + return DefaultGameSettings.defaultSizeValue; + } + + factory GameSettings.createDefault() { + return GameSettings( + level: DefaultGameSettings.defaultLevelValue, + size: DefaultGameSettings.defaultSizeValue, + ); + } + + void dump() { + printlog('$GameSettings:'); + printlog(' ${DefaultGameSettings.parameterCodeLevel}: $level'); + printlog(' ${DefaultGameSettings.parameterCodeSize}: $size'); + printlog(''); + } + + @override + String toString() { + return '$GameSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeLevel: level, + DefaultGameSettings.parameterCodeSize: size, + }; + } +} diff --git a/lib/models/settings/settings_global.dart b/lib/models/settings/settings_global.dart new file mode 100644 index 0000000000000000000000000000000000000000..9900685f127517bf6c5c700be997f178fde37958 --- /dev/null +++ b/lib/models/settings/settings_global.dart @@ -0,0 +1,41 @@ +import 'package:minehunter/config/default_global_settings.dart'; +import 'package:minehunter/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..5b6aee12bf30231f630550de6425ce0ef1262184 --- /dev/null +++ b/lib/models/types.dart @@ -0,0 +1,5 @@ +import 'package:minehunter/models/game/cell.dart'; + +typedef Board = List<List<Cell>>; +typedef AnimatedBoard = List<List<bool>>; +typedef AnimatedBoardSequence = List<AnimatedBoard>; diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index 0ab77100d03abc731f0b42d95f90ddace2436c3d..0000000000000000000000000000000000000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,292 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'package:minehunter/entities/cell.dart'; - -typedef Board = List<List<Cell>>; -typedef AnimatedBoard = List<List<bool>>; -typedef AnimatedBoardSequence = List<AnimatedBoard>; - -class Data extends ChangeNotifier { - // Configuration available parameters - final List<String> _availableParameters = ['level', 'size', 'skin']; - List<String> get availableParameters => _availableParameters; - - // Configuration available values - final List<String> _availableLevelValues = ['easy', 'medium', 'hard', 'nightmare']; - final List<String> _availableSizeValues = ['10x10', '15x15', '20x20']; - final List<String> _availableSkinValues = ['default']; - - List<String> get availableLevelValues => _availableLevelValues; - List<String> get availableSizeValues => _availableSizeValues; - List<String> get availableSkinValues => _availableSkinValues; - - // Application default configuration - String _parameterLevel = ''; - final String _parameterLevelDefault = 'medium'; - String _parameterSize = ''; - final String _parameterSizeDefault = '15x15'; - String _parameterSkin = ''; - final String _parameterSkinDefault = 'default'; - - // Application current configuration - String get parameterLevel => _parameterLevel; - String get parameterSize => _parameterSize; - String get parameterSkin => _parameterSkin; - - // Game data - bool _assetsPreloaded = false; - bool _gameIsRunning = false; - bool _animationInProgress = false; - int _sizeVertical = 0; - int _sizeHorizontal = 0; - Board _board = []; - bool _isBoardMined = false; - int _minesCount = 0; - bool _reportMode = false; - bool _gameWin = false; - bool _gameFail = false; - String _currentState = ''; - - void updateParameterLevel(String parameterLevel) { - _parameterLevel = parameterLevel; - notifyListeners(); - } - - int get sizeVertical => _sizeVertical; - int get sizeHorizontal => _sizeHorizontal; - void updateParameterSize(String parameterSize) { - _parameterSize = parameterSize; - _sizeHorizontal = int.parse(_parameterSize.split('x')[0]); - _sizeVertical = int.parse(_parameterSize.split('x')[1]); - notifyListeners(); - } - - void updateParameterSkin(String parameterSkin) { - _parameterSkin = parameterSkin; - notifyListeners(); - } - - String getParameterValue(String parameterCode) { - switch (parameterCode) { - case 'level': - { - return _parameterLevel; - } - case 'size': - { - return _parameterSize; - } - - case 'skin': - { - return _parameterSkin; - } - } - return ''; - } - - List<String> getParameterAvailableValues(String parameterCode) { - switch (parameterCode) { - case 'level': - { - return _availableLevelValues; - } - - case 'size': - { - return _availableSizeValues; - } - - case 'skin': - { - return _availableSkinValues; - } - } - return []; - } - - setParameterValue(String parameterCode, String parameterValue) async { - switch (parameterCode) { - case 'level': - { - updateParameterLevel(parameterValue); - } - break; - case 'size': - { - updateParameterSize(parameterValue); - } - break; - case 'skin': - { - updateParameterSkin(parameterValue); - } - break; - } - final prefs = await SharedPreferences.getInstance(); - prefs.setString(parameterCode, parameterValue); - } - - void initParametersValues() async { - final prefs = await SharedPreferences.getInstance(); - setParameterValue('level', prefs.getString('level') ?? _parameterLevelDefault); - setParameterValue('size', prefs.getString('size') ?? _parameterSizeDefault); - setParameterValue('skin', prefs.getString('skin') ?? _parameterSkinDefault); - } - - String get currentState => _currentState; - - String computeCurrentGameState() { - String cellsValues = ''; - for (int rowIndex = 0; rowIndex < _board.length; rowIndex++) { - for (int colIndex = 0; colIndex < _board[rowIndex].length; colIndex++) { - cellsValues += _board[rowIndex][colIndex].isMined ? 'X' : ' '; - cellsValues += _board[rowIndex][colIndex].isExplored ? 'E' : ' '; - cellsValues += _board[rowIndex][colIndex].isMarked ? 'P' : ' '; - cellsValues += _board[rowIndex][colIndex].isExploded ? '*' : ' '; - cellsValues += _board[rowIndex][colIndex].minesCountAround.toString(); - cellsValues += ';'; - } - } - - var currentState = { - 'level': _parameterLevel, - 'size': _parameterSize, - 'skin': _parameterSkin, - 'board': cellsValues, - }; - - return json.encode(currentState); - } - - void saveCurrentGameState() async { - if (_gameIsRunning) { - _currentState = computeCurrentGameState(); - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('savedState', _currentState); - } else { - resetCurrentSavedState(); - } - } - - void resetCurrentSavedState() async { - _currentState = ''; - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('savedState', _currentState); - notifyListeners(); - } - - void loadCurrentSavedState() async { - final prefs = await SharedPreferences.getInstance(); - _currentState = prefs.getString('savedState') ?? ''; - } - - bool hasCurrentSavedState() { - return (_currentState != ''); - } - - Map<String, dynamic> getCurrentSavedState() { - if (_currentState != '') { - Map<String, dynamic> savedState = json.decode(_currentState); - if (savedState.isNotEmpty) { - return savedState; - } - } - return {}; - } - - bool get gameIsRunning => _gameIsRunning; - void updateGameIsRunning(bool gameIsRunning) { - _gameIsRunning = gameIsRunning; - updateGameWin(false); - updateGameFail(false); - updateReportMode(false); - notifyListeners(); - } - - bool get gameWin => _gameWin; - void updateGameWin(bool gameWin) { - _gameWin = gameWin; - notifyListeners(); - } - - bool get gameFail => _gameFail; - void updateGameFail(bool gameFail) { - _gameFail = gameFail; - notifyListeners(); - } - - Board get board => _board; - void updateBoard(Board board) { - _board = board; - notifyListeners(); - } - - void setCellAsExplored(int row, int col) { - _board[row][col].isExplored = true; - _board[row][col].isMarked = false; - if (_board[row][col].isMined) { - _board[row][col].isExploded = true; - } - - saveCurrentGameState(); - notifyListeners(); - } - - void toggleCellMark(int row, int col) { - _board[row][col].isMarked = !_board[row][col].isMarked; - - saveCurrentGameState(); - notifyListeners(); - } - - bool get assetsPreloaded => _assetsPreloaded; - void updateAssetsPreloaded(bool assetsPreloaded) { - _assetsPreloaded = assetsPreloaded; - } - - bool get isBoardMined => _isBoardMined; - void updateIsBoardMined(bool isBoardMined) { - _isBoardMined = isBoardMined; - notifyListeners(); - } - - int get minesCount => _minesCount; - void updateMinesCount(int minesCount) { - _minesCount = minesCount; - } - - bool get reportMode => _reportMode; - void updateReportMode(bool reportMode) { - _reportMode = reportMode; - notifyListeners(); - } - - bool get animationInProgress => _animationInProgress; - void updateAnimationInProgress(bool animationInProgress) { - _animationInProgress = animationInProgress; - notifyListeners(); - } - - void setAnimatedBackground(List animatedCellsPattern) { - for (int row = 0; row < _sizeVertical; row++) { - for (int col = 0; col < _sizeHorizontal; col++) { - _board[row][col].isAnimated = animatedCellsPattern[row][col]; - } - } - notifyListeners(); - } - - void resetAnimatedBackground() { - for (int row = 0; row < _sizeVertical; row++) { - for (int col = 0; col < _sizeHorizontal; col++) { - _board[row][col].isAnimated = false; - } - } - } -} diff --git a/lib/ui/game/game_bottom.dart b/lib/ui/game/game_bottom.dart new file mode 100644 index 0000000000000000000000000000000000000000..6e1e63bf1f36b4bf85a4104db466caae0fd2c882 --- /dev/null +++ b/lib/ui/game/game_bottom.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/ui/widgets/game/mode_toggle.dart'; + +class GameBottomWidget extends StatelessWidget { + const GameBottomWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return !currentGame.isFinished ? const ToggleGameMode() : const SizedBox.shrink(); + }, + ); + } +} diff --git a/lib/ui/game/game_end.dart b/lib/ui/game/game_end.dart new file mode 100644 index 0000000000000000000000000000000000000000..d6f07b1cbd81bba9cd179e142fae8602337431c3 --- /dev/null +++ b/lib/ui/game/game_end.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/ui/widgets/actions/button_game_quit.dart'; + +class GameEndWidget extends StatelessWidget { + const GameEndWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final Image decorationImage = Image( + image: AssetImage(currentGame.gameWin + ? 'assets/ui/game_win.png' + : currentGame.gameFail + ? 'assets/ui/game_fail.png' + : ''), + fit: BoxFit.fill, + ); + + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + TableRow( + children: [ + Column( + children: [decorationImage], + ), + Column( + children: [ + currentGame.animationInProgress + ? decorationImage + : const QuitGameButton() + ], + ), + Column( + children: [decorationImage], + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/game/game_top.dart b/lib/ui/game/game_top.dart new file mode 100644 index 0000000000000000000000000000000000000000..6b60094966fa6a7bb35b8830520426a6237304d5 --- /dev/null +++ b/lib/ui/game/game_top.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:minehunter/ui/widgets/indicators/indicator_top.dart'; + + +class GameTopWidget extends StatelessWidget { + const GameTopWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const TopIndicator(); + } +} diff --git a/lib/ui/helpers/app_titles.dart b/lib/ui/helpers/app_titles.dart new file mode 100644 index 0000000000000000000000000000000000000000..b98107b12fabc3114ebfbec994166b588abcf1ad --- /dev/null +++ b/lib/ui/helpers/app_titles.dart @@ -0,0 +1,32 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class AppHeader extends StatelessWidget { + const AppHeader({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + ); + } +} + +class AppTitle extends StatelessWidget { + const AppTitle({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.titleLarge!.apply(fontWeightDelta: 2), + ); + } +} diff --git a/lib/ui/helpers/outlined_text_widget.dart b/lib/ui/helpers/outlined_text_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..b450f6b1c6323e0d645059e4d367ec4987891258 --- /dev/null +++ b/lib/ui/helpers/outlined_text_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +import 'package:minehunter/utils/color_extensions.dart'; + +class OutlinedText extends StatelessWidget { + const OutlinedText({ + super.key, + required this.text, + required this.fontSize, + required this.textColor, + this.outlineColor, + }); + + final String text; + final double fontSize; + final Color textColor; + final Color? outlineColor; + + @override + Widget build(BuildContext context) { + final double delta = fontSize / 30; + + return Text( + text, + style: TextStyle( + inherit: true, + fontSize: fontSize, + fontWeight: FontWeight.w600, + color: textColor, + shadows: [ + Shadow( + offset: Offset(-delta, -delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(delta, -delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(delta, delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(-delta, delta), + color: outlineColor ?? textColor.darken(), + ), + ], + ), + ); + } +} diff --git a/lib/ui/layout/game.dart b/lib/ui/layout/game.dart deleted file mode 100644 index 36796492ca10e14dc7f8d78aa8251118ec275ced..0000000000000000000000000000000000000000 --- a/lib/ui/layout/game.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/ui/layout/tileset.dart'; -import 'package:minehunter/ui/widgets/game/indicator_top.dart'; -import 'package:minehunter/ui/widgets/game/message_game_end.dart'; -import 'package:minehunter/ui/widgets/game/mode_toggle.dart'; - -class Game extends StatelessWidget { - const Game({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final bool gameIsFinished = myProvider.gameWin || myProvider.gameFail; - - 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) - : ToggleGameMode(myProvider: myProvider), - ), - const SizedBox(height: 8), - ], - ); - } -} diff --git a/lib/ui/layout/parameters.dart b/lib/ui/layout/parameters.dart deleted file mode 100644 index ee2502a4129b96df781a6d66b1156d56ee17f9a3..0000000000000000000000000000000000000000 --- a/lib/ui/layout/parameters.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/ui/widgets/home/button_game_resume.dart'; -import 'package:minehunter/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 = 3.0; - static const double blockPadding = 2.0; - static const Color buttonBackgroundColor = Colors.white; - static const Color buttonBorderColorActive = Colors.blue; - static const Color buttonBorderColorInactive = Colors.white; - static const double buttonBorderWidth = 10.0; - static const double buttonBorderRadius = 8.0; - static const double buttonPadding = 0.0; - static const double buttonMargin = 0.0; - - @override - Widget build(BuildContext context) { - List<Widget> lines = []; - - final List<String> 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) { - final List<String> 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) { - final String currentValue = myProvider.getParameterValue(parameterCode).toString(); - - final bool isActive = (parameterValue == currentValue); - final 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 f5986aab892ea72467bd80636da3ee85c1cf600f..0000000000000000000000000000000000000000 --- a/lib/ui/layout/tileset.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/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; - final Color borderColor = myProvider.reportMode ? Colors.blue : Colors.black; - - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Column( - children: [ - Container( - decoration: BoxDecoration( - color: borderColor, - borderRadius: BorderRadius.circular(2), - border: Border.all( - color: borderColor, - width: 2, - ), - ), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - for (int row = 0; row < myProvider.sizeVertical; row++) - TableRow( - children: [ - for (int col = 0; col < myProvider.sizeHorizontal; col++) - Column( - children: [ - board[row][col].widget(myProvider, row, col), - ], - ), - ], - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/ui/layouts/game_layout.dart b/lib/ui/layouts/game_layout.dart new file mode 100644 index 0000000000000000000000000000000000000000..05180ea903dbd732ab93a4b7875692928f3d5495 --- /dev/null +++ b/lib/ui/layouts/game_layout.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/ui/game/game_bottom.dart'; +import 'package:minehunter/ui/game/game_end.dart'; +import 'package:minehunter/ui/game/game_top.dart'; +import 'package:minehunter/ui/widgets/game/game_board.dart'; + +class GameLayout extends StatelessWidget { + const GameLayout({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return Container( + alignment: AlignmentDirectional.topCenter, + padding: const EdgeInsets.all(4), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const GameTopWidget(), + const SizedBox(height: 8), + const GameBoardWidget(), + const SizedBox(height: 8), + const GameBottomWidget(), + const Expanded(child: SizedBox.shrink()), + currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/layouts/parameters_layout.dart b/lib/ui/layouts/parameters_layout.dart new file mode 100644 index 0000000000000000000000000000000000000000..de36efd4e35431508ac96eed770cbe4376bee78d --- /dev/null +++ b/lib/ui/layouts/parameters_layout.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/config/default_game_settings.dart'; +import 'package:minehunter/config/default_global_settings.dart'; +import 'package:minehunter/cubit/settings_game_cubit.dart'; +import 'package:minehunter/cubit/settings_global_cubit.dart'; +import 'package:minehunter/ui/parameters/parameter_image.dart'; +import 'package:minehunter/ui/parameters/parameter_painter.dart'; +import 'package:minehunter/ui/widgets/actions/button_delete_saved_game.dart'; +import 'package:minehunter/ui/widgets/actions/button_game_start_new.dart'; +import 'package:minehunter/ui/widgets/actions/button_resume_saved_game.dart'; + +class ParametersLayout extends StatelessWidget { + const ParametersLayout({super.key, required this.canResume}); + + final bool canResume; + + 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)); + + if (canResume == false) { + // Start new game + lines.add(const Expanded( + child: StartNewGameButton(), + )); + } else { + // Resume game + lines.add(const Expanded( + child: ResumeSavedGameButton(), + )); + // Delete saved game + lines.add(SizedBox.square( + dimension: MediaQuery.of(context).size.width / 4, + child: const DeleteSavedGameButton(), + )); + } + + lines.add(SizedBox(height: separatorHeight)); + + // Global settings + for (String code in DefaultGlobalSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: true, + ), + )); + + lines.add(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; + + final bool displayedWithAssets = + DefaultGlobalSettings.displayedWithAssets.contains(code) || + DefaultGameSettings.displayedWithAssets.contains(code); + + return TextButton( + child: Container( + child: displayedWithAssets + ? SizedBox.square( + dimension: itemWidth, + child: ParameterImage( + code: code, + value: value, + isSelected: isActive, + ), + ) + : 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/ui/parameters/parameter_image.dart b/lib/ui/parameters/parameter_image.dart new file mode 100644 index 0000000000000000000000000000000000000000..fc4b576f85b01158b74548400d11a4d027c57fbe --- /dev/null +++ b/lib/ui/parameters/parameter_image.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +class ParameterImage extends StatelessWidget { + const ParameterImage({ + super.key, + required this.code, + required this.value, + required this.isSelected, + }); + + final String code; + final String value; + final bool isSelected; + + static const Color buttonBackgroundColor = Colors.white; + static const Color buttonBorderColorActive = Colors.blue; + static const Color buttonBorderColorInactive = Colors.white; + static const double buttonBorderWidth = 8.0; + static const double buttonBorderRadius = 8.0; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: buttonBackgroundColor, + borderRadius: BorderRadius.circular(buttonBorderRadius), + border: Border.all( + color: isSelected ? buttonBorderColorActive : buttonBorderColorInactive, + width: buttonBorderWidth, + ), + ), + child: Image( + image: AssetImage('assets/ui/${code}_$value.png'), + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/ui/parameters/parameter_painter.dart b/lib/ui/parameters/parameter_painter.dart new file mode 100644 index 0000000000000000000000000000000000000000..e00cf06d4cdfad6d3e62e345e0d7007e16ec5a90 --- /dev/null +++ b/lib/ui/parameters/parameter_painter.dart @@ -0,0 +1,226 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:minehunter/config/default_game_settings.dart'; +import 'package:minehunter/models/settings/settings_game.dart'; +import 'package:minehunter/models/settings/settings_global.dart'; +import 'package:minehunter/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.parameterCodeLevel: + paintLevelParameterItem(value, canvas, canvasSize); + break; + case DefaultGameSettings.parameterCodeSize: + paintSizeParameterItem(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 paintLevelParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + + final List<dynamic> stars = []; + + switch (value) { + case DefaultGameSettings.levelValueEasy: + backgroundColor = Colors.green; + stars.add([0.5, 0.5]); + break; + case DefaultGameSettings.levelValueMedium: + backgroundColor = Colors.orange; + stars.add([0.3, 0.5]); + stars.add([0.7, 0.5]); + break; + case DefaultGameSettings.levelValueHard: + backgroundColor = Colors.red; + stars.add([0.3, 0.3]); + stars.add([0.7, 0.3]); + stars.add([0.5, 0.7]); + break; + case DefaultGameSettings.levelValueNightmare: + backgroundColor = Colors.purple; + stars.add([0.3, 0.3]); + stars.add([0.7, 0.3]); + stars.add([0.3, 0.7]); + stars.add([0.7, 0.7]); + break; + default: + printlog('Wrong value for level parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Stars + final textSpan = TextSpan( + text: '⭐', + style: TextStyle( + color: Colors.black, + fontSize: size / 3, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + + for (var center in stars) { + textPainter.paint( + canvas, + Offset( + size * center[0] - textPainter.width * 0.5, + size * center[1] - textPainter.height * 0.5, + ), + ); + } + } + + void paintSizeParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + int gridWidth = 1; + + switch (value) { + case DefaultGameSettings.sizeValueSmall: + backgroundColor = Colors.green; + gridWidth = 2; + break; + case DefaultGameSettings.sizeValueMedium: + backgroundColor = Colors.orange; + gridWidth = 3; + break; + case DefaultGameSettings.sizeValueLarge: + backgroundColor = Colors.red; + gridWidth = 4; + break; + default: + printlog('Wrong value for boardSize parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Mini grid + final squareColor = Colors.grey.shade200; + final borderColor = Colors.grey.shade800; + + final double cellSize = size / 7; + final double origin = (size - gridWidth * cellSize) / 2; + + for (int row = 0; row < gridWidth; row++) { + for (int col = 0; col < gridWidth; col++) { + final Offset topLeft = Offset(origin + col * cellSize, origin + row * cellSize); + final Offset bottomRight = topLeft + Offset(cellSize, cellSize); + + paint.color = squareColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + paint.color = borderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + } + } +} diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart deleted file mode 100644 index cce017474d37d22a29b137981cbdd6e2bd3eb8ae..0000000000000000000000000000000000000000 --- a/lib/ui/screens/game_page.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/ui/layout/game.dart'; -import 'package:minehunter/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(); - } - - List<String> getImagesAssets(Data myProvider) { - final List<String> assets = []; - - final List<String> gameImages = [ - 'button_back', - 'button_start', - 'game_fail', - 'game_win', - ]; - for (String level in myProvider.availableLevelValues) { - gameImages.add('level_$level'); - } - for (String size in myProvider.availableSizeValues) { - gameImages.add('size_$size'); - } - - for (String image in gameImages) { - assets.add('${'assets/icons/$image'}.png'); - } - - final List<String> skinImages = [ - 'button_mark_mine_off', - 'button_mark_mine_on', - 'indicator_report_on', - 'indicator_report_off', - 'indicator_walk_on', - 'indicator_walk_off', - 'tile_flag_ko', - 'tile_flag_ok', - 'tile_flag', - 'tile_mine_not_found', - 'tile_mine', - 'tile_unknown', - ]; - for (int value = 0; value < 9; value++) { - skinImages.add('tile_$value'); - } - - for (String image in skinImages) { - assets.add('${'assets/skins/default_$image'}.png'); - } - - assets.add('assets/skins/default_empty.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); - } - - 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 86% rename from lib/ui/screens/about_page.dart rename to lib/ui/screens/page_about.dart index 39efd8cc91f36d4d43227ff614a00e5bb1c60ddf..11329edb6e31fd66565b6747923c97e6c6694196 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:minehunter/ui/widgets/header_app.dart'; +import 'package:minehunter/ui/helpers/app_titles.dart'; -class AboutPage extends StatelessWidget { - const AboutPage({super.key}); +class PageAbout extends StatelessWidget { + const PageAbout({super.key}); @override Widget build(BuildContext context) { @@ -17,7 +17,7 @@ class AboutPage extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: <Widget>[ const SizedBox(height: 8), - const AppHeader(text: 'about_title'), + const AppTitle(text: 'about_title'), const Text('about_content').tr(), FutureBuilder<PackageInfo>( future: PackageInfo.fromPlatform(), diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..5598d23f6f77146a895e5e58ff488a52af9f324a --- /dev/null +++ b/lib/ui/screens/page_game.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/ui/layouts/game_layout.dart'; +import 'package:minehunter/ui/layouts/parameters_layout.dart'; + +class PageGame extends StatelessWidget { + const PageGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return currentGame.isRunning + ? const GameLayout() + : ParametersLayout(canResume: currentGame.canBeResumed); + }, + ); + } +} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/page_settings.dart similarity index 66% rename from lib/ui/screens/settings_page.dart rename to lib/ui/screens/page_settings.dart index 7f2146872b3e8a613ea407c3d3517d6c540e026e..3953586f9b1665d6c75ffc1901678963605c26d4 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:minehunter/ui/widgets/header_app.dart'; -import 'package:minehunter/ui/widgets/settings/settings_form.dart'; +import 'package:minehunter/ui/helpers/app_titles.dart'; +import 'package:minehunter/ui/settings/settings_form.dart'; -class SettingsPage extends StatelessWidget { - const SettingsPage({super.key}); +class PageSettings extends StatelessWidget { + const PageSettings({super.key}); @override Widget build(BuildContext context) { @@ -16,7 +16,7 @@ class SettingsPage extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: <Widget>[ SizedBox(height: 8), - AppHeader(text: 'settings_title'), + AppTitle(text: 'settings_title'), SizedBox(height: 8), SettingsForm(), ], diff --git a/lib/ui/widgets/settings/settings_form.dart b/lib/ui/settings/settings_form.dart similarity index 96% rename from lib/ui/widgets/settings/settings_form.dart rename to lib/ui/settings/settings_form.dart index 910c9507b2d9f4778e2e046faa35c93af55a6377..6806beafd9c70f66bbfbdc1ddffdb07bc7555e60 100644 --- a/lib/ui/widgets/settings/settings_form.dart +++ b/lib/ui/settings/settings_form.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:unicons/unicons.dart'; -import 'package:minehunter/ui/widgets/settings/theme_card.dart'; +import 'package:minehunter/ui/settings/theme_card.dart'; class SettingsForm extends StatefulWidget { const SettingsForm({super.key}); diff --git a/lib/ui/widgets/settings/theme_card.dart b/lib/ui/settings/theme_card.dart similarity index 100% rename from lib/ui/widgets/settings/theme_card.dart rename to lib/ui/settings/theme_card.dart diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index 34421224675da8740c9671342bad6c11e08699cb..0b0885a06d673ce7c762d5ae9446769b5d075619 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,42 +1,34 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; import 'package:minehunter/config/menu.dart'; -import 'package:minehunter/cubit/bottom_nav_cubit.dart'; -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/ui/widgets/app_bar.dart'; -import 'package:minehunter/ui/widgets/bottom_nav_bar.dart'; +import 'package:minehunter/cubit/nav_cubit.dart'; +import 'package:minehunter/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), - body: BlocBuilder<BottomNavCubit, int>( - builder: (BuildContext context, int pageIndex) { - return Padding( - padding: const EdgeInsets.only( - top: 8, - left: 2, - right: 2, - ), - child: Menu.getPageWidget(pageIndex), - ); - }, + body: Material( + color: Theme.of(context).colorScheme.surface, + child: BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + return Padding( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), + child: Menu.getPageWidget(pageIndex), + ); + }, + ), ), backgroundColor: Theme.of(context).colorScheme.surface, - bottomNavigationBar: const BottomNavBar(), ); } } diff --git a/lib/ui/widgets/actions/button_delete_saved_game.dart b/lib/ui/widgets/actions/button_delete_saved_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..8254982478b514fa7d7d9803f24f8d0b2a0d60d8 --- /dev/null +++ b/lib/ui/widgets/actions/button_delete_saved_game.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; + +class DeleteSavedGameButton extends StatelessWidget { + const DeleteSavedGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_delete_saved_game.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).deleteSavedGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/actions/button_game_quit.dart b/lib/ui/widgets/actions/button_game_quit.dart new file mode 100644 index 0000000000000000000000000000000000000000..544470bc63c2ea0fc7a268d44460a9d6e0d5215e --- /dev/null +++ b/lib/ui/widgets/actions/button_game_quit.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; + +class QuitGameButton extends StatelessWidget { + const QuitGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).quitGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/actions/button_game_start_new.dart b/lib/ui/widgets/actions/button_game_start_new.dart new file mode 100644 index 0000000000000000000000000000000000000000..f2d6de12474b57d6603762af1601a209aba0a53e --- /dev/null +++ b/lib/ui/widgets/actions/button_game_start_new.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/cubit/settings_game_cubit.dart'; +import 'package:minehunter/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) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_start.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ); + }, + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/actions/button_resume_saved_game.dart b/lib/ui/widgets/actions/button_resume_saved_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..803c9360b3ffea80e4e3ace196cbf97c2d26772d --- /dev/null +++ b/lib/ui/widgets/actions/button_resume_saved_game.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; + +class ResumeSavedGameButton extends StatelessWidget { + const ResumeSavedGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_resume_game.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).resumeSavedGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart deleted file mode 100644 index ae49c5c103d4e1a58e35cd3ffcb9024c28381478..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/app_bar.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/ui/widgets/header_app.dart'; -import 'package:minehunter/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( - onPressed: null, - onLongPress: () => GameUtils.quitGame(myProvider), - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - )); - } - - 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 58d476c10fc3ca45e8409cb59a1448e2728f1c97..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:minehunter/config/menu.dart'; -import 'package:minehunter/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.surfaceContainerHighest, - 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/game/cell.dart b/lib/ui/widgets/game/cell.dart new file mode 100644 index 0000000000000000000000000000000000000000..89279eeb5d3d4389bb9cff998eb5c36f4517d1b9 --- /dev/null +++ b/lib/ui/widgets/game/cell.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/cell.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/utils/board_animate.dart'; + +class CellWidget extends StatelessWidget { + const CellWidget({ + super.key, + required this.cell, + required this.row, + required this.col, + }); + + final Cell cell; + final int row; + final int col; + + @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); + + String imageAsset = getImageAssetName( + currentGame: currentGame, + ); + + return Container( + decoration: BoxDecoration( + color: getBackgroundColor(currentGame), + border: getCellBorders( + currentGame: currentGame, + row: row, + col: col, + ), + ), + child: GestureDetector( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 100), + transitionBuilder: (Widget child, Animation<double> animation) { + return ScaleTransition(scale: animation, child: child); + }, + child: Image( + image: AssetImage(imageAsset), + fit: BoxFit.fill, + key: ValueKey<int>(imageAsset.hashCode), + ), + ), + onTap: () { + // Set mines on board after first player try + if (!currentGame.isBoardMined) { + gameCubit.addMines(forbiddenRow: row, forbiddenCol: col); + } + + if (!(currentGame.gameWin || currentGame.gameFail)) { + if (currentGame.reportMode) { + gameCubit.reportCell( + row: row, + col: col, + ); + } else { + gameCubit.walkOnCell( + row: row, + col: col, + ); + } + if (gameCubit.checkGameIsFinished()) { + gameCubit.updateReportMode(false); + BoardAnimate.startAnimation( + gameCubit: gameCubit, + currentGame: currentGame, + animationType: currentGame.gameWin ? 'win' : 'fail', + ); + } + } + }, + ), + ); + }, + ); + } + + /* + * Compute image asset name, from skin and cell value/state + */ + String getImageAssetName({ + required Game currentGame, + }) { + final String skin = currentGame.globalSettings.skin; + + String imageAsset = 'assets/skins/${skin}_tile_unknown.png'; + + bool showSolution = currentGame.gameWin || currentGame.gameFail; + if (!showSolution) { + // Running game + if (cell.isExplored) { + if (cell.isMined) { + // Boom + imageAsset = 'assets/skins/${skin}_tile_mine.png'; + } else { + // Show mines count around + imageAsset = 'assets/skins/${skin}_tile_${cell.minesCountAround}.png'; + } + } else { + if (cell.isMarked) { + // Danger! + imageAsset = 'assets/skins/${skin}_tile_flag.png'; + } + } + } else { + // Finished game + if (cell.isMined) { + if (cell.isExploded) { + // Mine exploded + imageAsset = 'assets/skins/${skin}_tile_mine.png'; + } else { + // Mine not found + imageAsset = 'assets/skins/${skin}_tile_mine_not_found.png'; + } + } else { + // Show all mines counts + imageAsset = 'assets/skins/${skin}_tile_${cell.minesCountAround}.png'; + } + } + + return imageAsset; + } + + // Compute cell background color, from cell state + Color getBackgroundColor(currentGame) { + if (currentGame.gameWin) { + return cell.isAnimated ? Colors.green.shade400 : Colors.green.shade500; + } + if (currentGame.gameFail) { + return cell.isAnimated ? Colors.pink.shade200 : Colors.pink.shade400; + } + + return cell.isAnimated ? Colors.white : Colors.grey.shade200; + } + + // Compute cell borders, from board size and cell state + Border getCellBorders({ + required Game currentGame, + required int row, + required int col, + }) { + Color cellBorderColor = Colors.grey.shade500; + double cellBorderWidth = 4; + + // Reduce cell border width on big boards + int boardSize = currentGame.sizeHorizontal; + if (boardSize > 8) { + cellBorderWidth = 2; + if (boardSize > 10) { + cellBorderWidth = 1; + } + } + + if (currentGame.gameWin) { + cellBorderColor = Colors.green.shade700; + } else if (currentGame.gameFail) { + cellBorderColor = Colors.pink.shade300; + } + + Border borders = Border.all( + color: cellBorderColor, + width: cellBorderWidth, + ); + + return borders; + } +} diff --git a/lib/ui/widgets/game/game_board.dart b/lib/ui/widgets/game/game_board.dart new file mode 100644 index 0000000000000000000000000000000000000000..52a7c90764c3ff74a09212ce4918c7f2ce93738d --- /dev/null +++ b/lib/ui/widgets/game/game_board.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/models/types.dart'; +import 'package:minehunter/ui/widgets/game/cell.dart'; + +class GameBoardWidget extends StatelessWidget { + const GameBoardWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final Board board = currentGame.board; + final Color borderColor = currentGame.reportMode ? Colors.blue : Colors.black; + + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: borderColor, + borderRadius: BorderRadius.circular(2), + border: Border.all( + color: borderColor, + width: 2, + ), + ), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + for (int row = 0; row < currentGame.sizeVertical; row++) + TableRow( + children: [ + for (int col = 0; col < currentGame.sizeHorizontal; col++) + Column( + children: [ + CellWidget(cell: board[row][col], row: row, col: col) + ], + ), + ], + ), + ], + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/indicator_top.dart b/lib/ui/widgets/game/indicator_top.dart deleted file mode 100644 index bf9ea9af42c4b24332880420b92b73d0db0a97dd..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/game/indicator_top.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/utils/board_utils.dart'; - -class TopIndicator extends StatelessWidget { - const TopIndicator({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final int flaggedCellsCount = BoardUtils.countFlaggedCells(myProvider.board); - final int minesCount = myProvider.minesCount; - const double blockSize = 40; - - final Image flagIconBlock = Image( - image: AssetImage('assets/skins/${myProvider.parameterSkin}_tile_flag.png'), - fit: BoxFit.fill, - height: blockSize, - width: blockSize, - ); - final Image mineIconBlock = Image( - image: AssetImage('assets/skins/${myProvider.parameterSkin}_tile_mine.png'), - fit: BoxFit.fill, - height: blockSize, - width: blockSize, - ); - final Text markedMinesCountBlock = Text( - flaggedCellsCount.toString(), - style: TextStyle( - fontSize: blockSize, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ); - final Text placedMinesCountBlock = Text( - minesCount.toString(), - style: TextStyle( - fontSize: blockSize, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ); - - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - markedMinesCountBlock, - flagIconBlock, - const SizedBox(width: blockSize * 2), - mineIconBlock, - placedMinesCountBlock, - ], - ) - ], - ); - } -} diff --git a/lib/ui/widgets/game/message_game_end.dart b/lib/ui/widgets/game/message_game_end.dart deleted file mode 100644 index 9eff7f183a81f104fd60228649a1a8745c525a2a..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/game/message_game_end.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/ui/widgets/home/button_game_restart.dart'; - -class EndGameMessage extends StatelessWidget { - const EndGameMessage({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final Image decorationImage = Image( - image: AssetImage(myProvider.gameWin - ? 'assets/icons/game_win.png' - : myProvider.gameFail - ? 'assets/icons/game_fail.png' - : ''), - fit: BoxFit.fill, - ); - - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Column( - children: [decorationImage], - ), - Column( - children: [ - myProvider.animationInProgress - ? decorationImage - : RestartGameButton(myProvider: myProvider) - ], - ), - Column( - children: [decorationImage], - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/game/mode_button_toggle.dart b/lib/ui/widgets/game/mode_button_toggle.dart index 7f6f7aae3c52a5725871fc8839a07e5c03f4582d..492e1b36806c9e4537171e951d8a2a2f3459462d 100644 --- a/lib/ui/widgets/game/mode_button_toggle.dart +++ b/lib/ui/widgets/game/mode_button_toggle.dart @@ -1,23 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:minehunter/provider/data.dart'; +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; class ToggleGameModeButton extends StatelessWidget { - const ToggleGameModeButton({super.key, required this.myProvider}); - - final Data myProvider; + const ToggleGameModeButton({super.key}); @override Widget build(BuildContext context) { - final String reportModeSuffix = myProvider.reportMode ? 'on' : 'off'; + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final String skin = currentGame.globalSettings.skin; + final String reportModeSuffix = currentGame.reportMode ? 'on' : 'off'; - return TextButton( - child: Image( - image: AssetImage( - 'assets/skins/${myProvider.parameterSkin}_button_mark_mine_$reportModeSuffix.png'), - fit: BoxFit.fill, - ), - onPressed: () => myProvider.updateReportMode(!myProvider.reportMode), + return TextButton( + child: Image( + image: AssetImage('assets/skins/${skin}_button_mark_mine_$reportModeSuffix.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).updateReportMode(!currentGame.reportMode); + }, + ); + }, ); } } diff --git a/lib/ui/widgets/game/mode_indicator_report.dart b/lib/ui/widgets/game/mode_indicator_report.dart index 6387149c844be9c5e056aa06ad143ca820082866..29af1fdd62deeca2497755bb5ce172b9bd147536 100644 --- a/lib/ui/widgets/game/mode_indicator_report.dart +++ b/lib/ui/widgets/game/mode_indicator_report.dart @@ -1,23 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:minehunter/provider/data.dart'; +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; class GameModeIndicatorReport extends StatelessWidget { - const GameModeIndicatorReport({super.key, required this.myProvider}); - - final Data myProvider; + const GameModeIndicatorReport({super.key}); @override Widget build(BuildContext context) { - final String reportModeSuffix = myProvider.reportMode ? 'on' : 'off'; + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final String skin = currentGame.globalSettings.skin; + final String reportModeSuffix = currentGame.reportMode ? 'on' : 'off'; - return TextButton( - child: Image( - image: AssetImage( - 'assets/skins/${myProvider.parameterSkin}_indicator_report_$reportModeSuffix.png'), - fit: BoxFit.fill, - ), - onPressed: () => myProvider.updateReportMode(true), + return TextButton( + child: Image( + image: AssetImage('assets/skins/${skin}_indicator_report_$reportModeSuffix.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).updateReportMode(true); + }, + ); + }, ); } } diff --git a/lib/ui/widgets/game/mode_indicator_walk.dart b/lib/ui/widgets/game/mode_indicator_walk.dart index be4cef83e9adce5e990bbc0106f8267b7c22d1ed..d374d0b48f0f8e9f05154f9a1b7364780cdd8677 100644 --- a/lib/ui/widgets/game/mode_indicator_walk.dart +++ b/lib/ui/widgets/game/mode_indicator_walk.dart @@ -1,23 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:minehunter/provider/data.dart'; +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; class GameModeIndicatorWalk extends StatelessWidget { - const GameModeIndicatorWalk({super.key, required this.myProvider}); - - final Data myProvider; + const GameModeIndicatorWalk({super.key}); @override Widget build(BuildContext context) { - final String reportModeSuffix = myProvider.reportMode ? 'off' : 'on'; + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final String skin = currentGame.globalSettings.skin; + final String reportModeSuffix = currentGame.reportMode ? 'off' : 'on'; - return TextButton( - child: Image( - image: AssetImage( - 'assets/skins/${myProvider.parameterSkin}_indicator_walk_$reportModeSuffix.png'), - fit: BoxFit.fill, - ), - onPressed: () => myProvider.updateReportMode(false), + return TextButton( + child: Image( + image: AssetImage('assets/skins/${skin}_indicator_walk_$reportModeSuffix.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).updateReportMode(false); + }, + ); + }, ); } } diff --git a/lib/ui/widgets/game/mode_toggle.dart b/lib/ui/widgets/game/mode_toggle.dart index 6a438d6cf16a4791366f4cba64574ea2f73287d6..38b2cde9db83e7c30576d5124bdfdff8163334d1 100644 --- a/lib/ui/widgets/game/mode_toggle.dart +++ b/lib/ui/widgets/game/mode_toggle.dart @@ -1,35 +1,43 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:minehunter/provider/data.dart'; +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; import 'package:minehunter/ui/widgets/game/mode_button_toggle.dart'; import 'package:minehunter/ui/widgets/game/mode_indicator_report.dart'; import 'package:minehunter/ui/widgets/game/mode_indicator_walk.dart'; class ToggleGameMode extends StatelessWidget { - const ToggleGameMode({super.key, required this.myProvider}); - - final Data myProvider; + const ToggleGameMode({super.key}); @override Widget build(BuildContext context) { - final Image paddingBlock = Image( - image: AssetImage('assets/skins/${myProvider.parameterSkin}_empty.png'), - fit: BoxFit.fill, - ); + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final String skin = currentGame.globalSettings.skin; + + final Image paddingBlock = Image( + image: AssetImage('assets/skins/${skin}_empty.png'), + fit: BoxFit.fill, + ); - return Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( + return Table( + defaultColumnWidth: const IntrinsicColumnWidth(), children: [ - TableCell(child: paddingBlock), - TableCell(child: GameModeIndicatorWalk(myProvider: myProvider)), - TableCell(child: ToggleGameModeButton(myProvider: myProvider)), - TableCell(child: GameModeIndicatorReport(myProvider: myProvider)), - TableCell(child: paddingBlock), + TableRow( + children: [ + TableCell(child: paddingBlock), + const TableCell(child: GameModeIndicatorWalk()), + const TableCell(child: ToggleGameModeButton()), + const TableCell(child: GameModeIndicatorReport()), + TableCell(child: paddingBlock), + ], + ), ], - ), - ], + ); + }, ); } } diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..94b7b64b419e85b61c8b69b8bce8aab5346e4bdf --- /dev/null +++ b/lib/ui/widgets/global_app_bar.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/config/menu.dart'; +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/cubit/nav_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/ui/helpers/app_titles.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + final Game currentGame = gameState.currentGame; + + final List<Widget> menuActions = []; + + if (currentGame.isRunning && !currentGame.isFinished) { + menuActions.add(TextButton( + child: const Image( + image: AssetImage('assets/ui/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () {}, + onLongPress: () { + BlocProvider.of<GameCubit>(context).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 AppHeader(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/header_app.dart deleted file mode 100644 index bf54b77375fbd0260f876f2885d0572b71715383..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/header_app.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; - -class AppHeader extends StatelessWidget { - const AppHeader({super.key, required this.text}); - - final String text; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - tr(text), - textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineMedium!.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 2fecb871a622f85764e40a512fbed7948da3a53a..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/home/button_game_restart.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/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.quitGame(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 2a1cc71f359762fe09962f62b787912cb7ddf351..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/home/button_game_resume.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/ui/layout/parameters.dart'; -import 'package:minehunter/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 1d58c490152db767a5a0a64c5a5ce5655f2ef6ec..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/home/button_game_start_new.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/ui/layout/parameters.dart'; -import 'package:minehunter/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/indicators/indicator_top.dart b/lib/ui/widgets/indicators/indicator_top.dart new file mode 100644 index 0000000000000000000000000000000000000000..3f4cdfda0ecc6dc40aaa495c80ca29ac3df0cc28 --- /dev/null +++ b/lib/ui/widgets/indicators/indicator_top.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; + +class TopIndicator extends StatelessWidget { + const TopIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final String skin = currentGame.globalSettings.skin; + + final int flaggedCellsCount = currentGame.countFlaggedCells(); + final int minesCount = currentGame.minesCount; + const double blockSize = 40; + + final Image flagIconBlock = Image( + image: AssetImage('assets/skins/${skin}_tile_flag.png'), + fit: BoxFit.fill, + height: blockSize, + width: blockSize, + ); + final Image mineIconBlock = Image( + image: AssetImage('assets/skins/${skin}_tile_mine.png'), + fit: BoxFit.fill, + height: blockSize, + width: blockSize, + ); + final Text markedMinesCountBlock = Text( + flaggedCellsCount.toString(), + style: TextStyle( + fontSize: blockSize, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + ); + final Text placedMinesCountBlock = Text( + minesCount.toString(), + style: TextStyle( + fontSize: blockSize, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + ); + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + markedMinesCountBlock, + flagIconBlock, + const SizedBox(width: blockSize * 2), + mineIconBlock, + placedMinesCountBlock, + ], + ) + ], + ); + }, + ); + } +} diff --git a/lib/utils/board_animate.dart b/lib/utils/board_animate.dart index 45e1352a3469ffe2331713491cb2291c53f3dbd4..69a3fa456b07de17807332a8e589316ff92bf0ec 100644 --- a/lib/utils/board_animate.dart +++ b/lib/utils/board_animate.dart @@ -1,16 +1,20 @@ import 'dart:async'; import 'dart:math'; -import 'package:minehunter/provider/data.dart'; +import 'package:minehunter/cubit/game_cubit.dart'; +import 'package:minehunter/models/game/game.dart'; +import 'package:minehunter/models/types.dart'; class BoardAnimate { // Start game animation: blinking tiles - static AnimatedBoardSequence createStartGameAnimationPatterns(Data myProvider) { + static AnimatedBoardSequence createStartGameAnimationPatterns({ + required Game currentGame, + }) { final AnimatedBoardSequence patterns = []; const int patternsCount = 4; - final int sizeHorizontal = myProvider.sizeHorizontal; - final int sizeVertical = myProvider.sizeVertical; + final int sizeHorizontal = currentGame.sizeHorizontal; + final int sizeVertical = currentGame.sizeVertical; for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) { final AnimatedBoard pattern = []; @@ -28,16 +32,18 @@ class BoardAnimate { } // Failed game animation: explosions blowing from exploded mines - static AnimatedBoardSequence createExplosionAnimationPatterns(Data myProvider) { + static AnimatedBoardSequence createExplosionAnimationPatterns({ + required Game currentGame, + }) { final AnimatedBoardSequence patterns = []; - final int sizeHorizontal = myProvider.sizeHorizontal; - final int sizeVertical = myProvider.sizeVertical; + final int sizeHorizontal = currentGame.sizeHorizontal; + final int sizeVertical = currentGame.sizeVertical; final List<List<int>> explodedMines = []; for (int row = 0; row < sizeVertical; row++) { for (int col = 0; col < sizeHorizontal; col++) { - if (myProvider.board[row][col].isExploded) { + if (currentGame.board[row][col].isExploded) { explodedMines.add([row, col]); } } @@ -70,12 +76,14 @@ class BoardAnimate { } // Win game animation: rotating rays from center - static AnimatedBoardSequence createWinGameAnimationPatterns(Data myProvider) { + static AnimatedBoardSequence createWinGameAnimationPatterns({ + required Game currentGame, + }) { final AnimatedBoardSequence patterns = []; const int patternsCount = 20; - final int sizeHorizontal = myProvider.sizeHorizontal; - final int sizeVertical = myProvider.sizeVertical; + final int sizeHorizontal = currentGame.sizeHorizontal; + final int sizeVertical = currentGame.sizeVertical; for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) { AnimatedBoard pattern = []; @@ -94,12 +102,14 @@ class BoardAnimate { } // Default multi-purpose animation: sliding stripes, from top left to right bottom - static AnimatedBoardSequence createDefaultAnimationPatterns(Data myProvider) { + static AnimatedBoardSequence createDefaultAnimationPatterns({ + required Game currentGame, + }) { final AnimatedBoardSequence patterns = []; const int patternsCount = 16; - final int sizeHorizontal = myProvider.sizeHorizontal; - final int sizeVertical = myProvider.sizeVertical; + final int sizeHorizontal = currentGame.sizeHorizontal; + final int sizeVertical = currentGame.sizeVertical; for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) { final AnimatedBoard pattern = []; @@ -116,26 +126,38 @@ class BoardAnimate { return patterns; } - static void startAnimation(Data myProvider, String animationType) { + static void startAnimation({ + required GameCubit gameCubit, + required Game currentGame, + required String animationType, + }) { AnimatedBoardSequence patterns = []; switch (animationType) { case 'start': - patterns = createStartGameAnimationPatterns(myProvider); + patterns = createStartGameAnimationPatterns( + currentGame: currentGame, + ); break; case 'win': - patterns = createWinGameAnimationPatterns(myProvider); + patterns = createWinGameAnimationPatterns( + currentGame: currentGame, + ); break; case 'fail': - patterns = createExplosionAnimationPatterns(myProvider); + patterns = createExplosionAnimationPatterns( + currentGame: currentGame, + ); break; default: - patterns = createDefaultAnimationPatterns(myProvider); + patterns = createDefaultAnimationPatterns( + currentGame: currentGame, + ); } int patternIndex = patterns.length; - myProvider.updateAnimationInProgress(true); + gameCubit.updateAnimationInProgress(true); const interval = Duration(milliseconds: 200); Timer.periodic( @@ -143,11 +165,11 @@ class BoardAnimate { (Timer timer) { if (patternIndex == 0) { timer.cancel(); - myProvider.resetAnimatedBackground(); - myProvider.updateAnimationInProgress(false); + gameCubit.resetAnimatedBackground(); + gameCubit.updateAnimationInProgress(false); } else { patternIndex--; - myProvider.setAnimatedBackground(patterns[patternIndex]); + gameCubit.setAnimatedBackground(patterns[patternIndex]); } }, ); diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart deleted file mode 100644 index 695d5bf72c84b1a53c86b7ea1785e5da9e520e1d..0000000000000000000000000000000000000000 --- a/lib/utils/board_utils.dart +++ /dev/null @@ -1,280 +0,0 @@ -import 'dart:math'; - -import 'package:minehunter/entities/cell.dart'; -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/utils/tools.dart'; - -class BoardUtils { - static printGrid(List cells) { - const String isMined = 'X'; - const String isSafe = '.'; - - const String mineFound = '#'; - const String wrongMarkedCell = '0'; - const String exploredSafeCell = '.'; - const String unkownState = ' '; - - printlog(''); - String line = '--'; - for (int i = 0; i < cells[0].length; i++) { - line += '-'; - } - printlog('$line $line'); - for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { - String currentLine = ''; - String solvedLine = ''; - for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { - solvedLine += cells[rowIndex][colIndex].isMined ? isMined : isSafe; - - String cellString = unkownState; - if (cells[rowIndex][colIndex].isExplored) { - cellString = exploredSafeCell; - } - if (cells[rowIndex][colIndex].isMarked) { - if (cells[rowIndex][colIndex].isMined) { - cellString = mineFound; - } else { - cellString = wrongMarkedCell; - } - } - currentLine += cellString; - } - printlog('|$currentLine| |$solvedLine|'); - } - printlog('$line $line'); - printlog(''); - } - - static Board createEmptyBoard(int sizeHorizontal, int sizeVertical) { - final Board board = []; - for (int rowIndex = 0; rowIndex < sizeVertical; rowIndex++) { - final List<Cell> row = []; - for (int colIndex = 0; colIndex < sizeHorizontal; colIndex++) { - row.add(Cell(false)); - } - board.add(row); - } - - return board; - } - - static int getMinesCount(int sizeHorizontal, int sizeVertical, String level) { - int minesCountRatio = 0; - switch (level) { - case 'easy': - { - minesCountRatio = 5; - } - break; - case 'medium': - { - minesCountRatio = 10; - } - break; - case 'hard': - { - minesCountRatio = 15; - } - break; - case 'nightmare': - { - minesCountRatio = 20; - } - break; - } - - final int minesCount = ((sizeHorizontal * sizeVertical) * minesCountRatio / 100).round(); - - printlog('Mines count: $minesCount'); - - return minesCount; - } - - static void createInitialEmptyBoard(Data myProvider) { - myProvider.updateIsBoardMined(false); - myProvider - .updateBoard(createEmptyBoard(myProvider.sizeHorizontal, myProvider.sizeVertical)); - } - - static Board createBoard(Data myProvider, int forbiddenRow, int forbiddenCol) { - final Board board = myProvider.board; - final int sizeHorizontal = myProvider.sizeHorizontal; - final int sizeVertical = myProvider.sizeVertical; - - // Shuffle cells to put random mines, except on currently selected one - final List<List<int>> allowedCells = []; - for (int row = 0; row < sizeVertical; row++) { - for (int col = 0; col < sizeHorizontal; col++) { - if (!((forbiddenRow == row) && (forbiddenCol == col))) { - allowedCells.add([row, col]); - } - } - } - allowedCells.shuffle(); - - // Put random mines on board - for (int mineIndex = 0; mineIndex < myProvider.minesCount; mineIndex++) { - board[allowedCells[mineIndex][0]][allowedCells[mineIndex][1]].isMined = true; - } - - // Compute all mines counts on cells - for (int row = 0; row < sizeVertical; row++) { - for (int col = 0; col < sizeHorizontal; col++) { - board[row][col].minesCountAround = getMinesCountAround(board, row, col); - } - } - - printGrid(board); - - return board; - } - - static Board createBoardFromSavedState(Data myProvider, String savedBoard) { - final Board board = []; - final int boardSize = pow((savedBoard.length / 6), 1 / 2).round(); - final String boardSizeAsString = '${boardSize}x$boardSize'; - myProvider.updateParameterSize(boardSizeAsString); - - int index = 0; - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - final List<Cell> row = []; - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - final bool isMined = (savedBoard[index++] == 'X'); - final bool isExplored = (savedBoard[index++] == 'E'); - final bool isMarked = (savedBoard[index++] == 'P'); - final bool isExploded = (savedBoard[index++] == '*'); - final int minesCountAround = int.parse(savedBoard[index++]); - index++; // ";" - - Cell cell = Cell(isMined); - cell.isExplored = isExplored; - cell.isMarked = isMarked; - cell.isExploded = isExploded; - cell.minesCountAround = minesCountAround; - - row.add(cell); - } - board.add(row); - } - - printGrid(board); - - return board; - } - - static void reportCell(Data myProvider, int row, int col) { - if (!myProvider.board[row][col].isExplored) { - myProvider.toggleCellMark(row, col); - } - } - - static void walkOnCell(Data myProvider, int row, int col) { - myProvider.setCellAsExplored(row, col); - - if (myProvider.board[row][col].minesCountAround == 0) { - final List<List<int>> safeCells = getAllSafeCellsAround(myProvider.board, row, col); - for (int safeCellIndex = 0; safeCellIndex < safeCells.length; safeCellIndex++) { - final int safeCellRow = safeCells[safeCellIndex][0]; - final int safeCellCol = safeCells[safeCellIndex][1]; - if (!myProvider.board[safeCellRow][safeCellCol].isExplored) { - walkOnCell(myProvider, safeCellRow, safeCellCol); - } - } - } - } - - static List<List<int>> getAllSafeCellsAround(List cells, int row, int col) { - final int sizeHorizontal = cells.length; - final int sizeVertical = cells[0].length; - - final List<List<int>> safeCellsCoordinates = []; - - if (cells[row][col].minesCountAround == 0) { - for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { - for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { - final int candidateRow = row + deltaRow; - final int candidateCol = col + deltaCol; - if ((candidateRow >= 0 && candidateRow < sizeVertical) && - (candidateCol >= 0 && candidateCol < sizeHorizontal) && - !cells[candidateRow][candidateCol].isExplored) { - safeCellsCoordinates.add([candidateRow, candidateCol]); - } - } - } - } - - return safeCellsCoordinates; - } - - static int getMinesCountAround(List cells, int row, int col) { - final int sizeHorizontal = cells.length; - final int sizeVertical = cells[0].length; - - int minesCountAround = 0; - for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { - for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { - if ((row + deltaRow >= 0 && row + deltaRow < sizeVertical) && - (col + deltaCol >= 0 && col + deltaCol < sizeHorizontal) && - (cells[row + deltaRow][col + deltaCol].isMined)) { - minesCountAround++; - } - } - } - - return minesCountAround; - } - - static bool checkGameIsFinished(Data myProvider) { - final Board board = myProvider.board; - final int sizeHorizontal = board.length; - final int sizeVertical = board[0].length; - - printGrid(board); - - myProvider.updateGameWin(false); - myProvider.updateGameFail(false); - - for (int row = 0; row < sizeVertical; row++) { - for (int col = 0; col < sizeHorizontal; col++) { - // Walked on a mine - if (board[row][col].isExploded == true) { - myProvider.updateGameFail(true); - return true; - } - } - } - - for (int row = 0; row < sizeVertical; row++) { - for (int col = 0; col < sizeHorizontal; col++) { - if ( - // Mine not already found - (board[row][col].isMined == true && board[row][col].isMarked == false) || - // Safe cell marked as mined - (board[row][col].isMined == false && board[row][col].isMarked == true)) { - return false; - } - } - } - - printlog('-> ok all mines found!'); - myProvider.updateGameWin(true); - - return true; - } - - static int countFlaggedCells(List cells) { - int count = 0; - - final int sizeHorizontal = cells.length; - final int sizeVertical = cells[0].length; - for (int row = 0; row < sizeVertical; row++) { - for (int col = 0; col < sizeHorizontal; col++) { - if (cells[row][col].isMarked == true) { - count++; - } - } - } - - return count; - } -} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97 --- /dev/null +++ b/lib/utils/color_extensions.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +extension ColorExtension on Color { + Color darken([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = 1 - percent / 100; + return Color.fromARGB( + alpha, + (red * value).round(), + (green * value).round(), + (blue * value).round(), + ); + } + + Color lighten([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = percent / 100; + return Color.fromARGB( + alpha, + (red + ((255 - red) * value)).round(), + (green + ((255 - green) * value)).round(), + (blue + ((255 - blue) * value)).round(), + ); + } + + Color avg(Color other) { + final red = (this.red + other.red) ~/ 2; + final green = (this.green + other.green) ~/ 2; + final blue = (this.blue + other.blue) ~/ 2; + final alpha = (this.alpha + other.alpha) ~/ 2; + return Color.fromARGB(alpha, red, green, blue); + } +} diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart deleted file mode 100644 index c173564b8f2348763847dfa7d0bf9faabdccec89..0000000000000000000000000000000000000000 --- a/lib/utils/game_utils.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:minehunter/provider/data.dart'; -import 'package:minehunter/utils/board_animate.dart'; -import 'package:minehunter/utils/board_utils.dart'; -import 'package:minehunter/utils/tools.dart'; - -class GameUtils { - static quitGame(Data myProvider) { - myProvider.updateGameIsRunning(false); - if (BoardUtils.checkGameIsFinished(myProvider)) { - myProvider.resetCurrentSavedState(); - } - } - - static void startNewGame(Data myProvider) { - printlog('Starting game: ${myProvider.parameterSize} - ${myProvider.parameterLevel}'); - myProvider.updateParameterSize(myProvider.parameterSize); - myProvider.updateMinesCount(BoardUtils.getMinesCount( - myProvider.sizeHorizontal, myProvider.sizeVertical, myProvider.parameterLevel)); - myProvider.updateGameIsRunning(true); - BoardUtils.createInitialEmptyBoard(myProvider); - BoardAnimate.startAnimation(myProvider, 'start'); - } - - static void deleteSavedGame(Data myProvider) { - myProvider.resetCurrentSavedState(); - } - - static void resumeSavedGame(Data myProvider) { - Map<String, dynamic> savedState = myProvider.getCurrentSavedState(); - if (savedState.isNotEmpty) { - try { - myProvider.setParameterValue('level', savedState['level']); - myProvider.setParameterValue('size', savedState['size']); - myProvider.setParameterValue('skin', savedState['skin']); - - myProvider.updateBoard( - BoardUtils.createBoardFromSavedState(myProvider, savedState['board'])); - 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); - } - } -} diff --git a/pubspec.lock b/pubspec.lock index 086064193c8ae5ca1019e066b4b108e7da2b6d13..e0ab96ebb656b1260018d45af586d9ec14ba4a7e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -106,10 +106,10 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a url: "https://pub.dev" source: hosted - version: "8.1.5" + version: "8.1.6" flutter_lints: dependency: "direct dev" description: @@ -217,7 +217,7 @@ packages: source: hosted version: "3.0.0" path: - dependency: "direct main" + dependency: transitive description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -236,10 +236,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.5" path_provider_foundation: dependency: transitive description: @@ -276,10 +276,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -289,7 +289,7 @@ packages: source: hosted version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c @@ -297,7 +297,7 @@ packages: source: hosted version: "6.1.2" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 @@ -308,10 +308,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_foundation: dependency: transitive description: @@ -439,4 +439,4 @@ packages: version: "1.0.4" sdks: dart: ">=3.4.0 <4.0.0" - flutter: ">=3.19.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index eedbd4cd9dae9e739394f1ce70e1b21ae09fbc2b..635202eacd52b8961e5d5d785d7c18ad3c6e3164 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,36 +1,38 @@ name: minehunter description: A minehunter game application. -publish_to: 'none' -version: 0.1.19+40 +publish_to: "none" + +version: 0.2.0+41 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: sdk: flutter + # base easy_localization: ^3.0.1 equatable: ^2.0.5 flutter_bloc: ^8.1.1 hive: ^2.2.3 hydrated_bloc: ^9.0.0 - provider: ^6.0.5 - shared_preferences: ^2.2.1 package_info_plus: ^8.0.0 - path: ^1.9.0 path_provider: ^2.0.11 unicons: ^2.1.1 + # specific + # (none) + dev_dependencies: flutter_lints: ^4.0.0 flutter: uses-material-design: true assets: - - assets/icons/ - assets/skins/ + - assets/ui/ - assets/translations/ fonts: diff --git a/icons/build_application_icons.sh b/resources/app/build_application_resources.sh similarity index 98% rename from icons/build_application_icons.sh rename to resources/app/build_application_resources.sh index 27dbe2647fe4e6d562fbd99451716d1b7d448570..6d67b8f4f9eca701d1aed7331ef41dfb0bd44f20 100755 --- a/icons/build_application_icons.sh +++ b/resources/app/build_application_resources.sh @@ -6,7 +6,7 @@ command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not ins command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; } CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -BASE_DIR="$(dirname "${CURRENT_DIR}")" +BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")" SOURCE_ICON="${CURRENT_DIR}/icon.svg" SOURCE_FASTLANE="${CURRENT_DIR}/featureGraphic.svg" diff --git a/icons/featureGraphic.svg b/resources/app/featureGraphic.svg similarity index 100% rename from icons/featureGraphic.svg rename to resources/app/featureGraphic.svg diff --git a/icons/icon.svg b/resources/app/icon.svg similarity index 100% rename from icons/icon.svg rename to resources/app/icon.svg diff --git a/resources/build_resources.sh b/resources/build_resources.sh new file mode 100755 index 0000000000000000000000000000000000000000..659697a1c043cfe1c7654635cfaec3e4a0ff8a1a --- /dev/null +++ b/resources/build_resources.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" + +${CURRENT_DIR}/app/build_application_resources.sh +${CURRENT_DIR}/ui/build_ui_resources.sh + diff --git a/resources/ui/build_ui_resources.sh b/resources/ui/build_ui_resources.sh new file mode 100755 index 0000000000000000000000000000000000000000..4f365ede7d83140ce6309a3083580f2662b30990 --- /dev/null +++ b/resources/ui/build_ui_resources.sh @@ -0,0 +1,110 @@ +#! /bin/bash + +# Check dependencies +command -v inkscape >/dev/null 2>&1 || { echo >&2 "I require inkscape but it's not installed. Aborting."; exit 1; } +command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not installed. Aborting."; exit 1; } +command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; } + +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")" +ASSETS_DIR="${BASE_DIR}/assets" + +OPTIPNG_OPTIONS="-preserve -quiet -o7" +ICON_SIZE=192 + +####################################################### + +# Game images (svg files found in `images` folder) +AVAILABLE_GAME_IMAGES="" +if [ -d "${CURRENT_DIR}/images" ]; then + AVAILABLE_GAME_IMAGES="$(find "${CURRENT_DIR}/images" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort)" +fi + +# Skins (subfolders found in `skins` folder) +AVAILABLE_SKINS="" +if [ -d "${CURRENT_DIR}/skins" ]; then + AVAILABLE_SKINS="$(find "${CURRENT_DIR}/skins" -mindepth 1 -type d | awk -F/ '{print $NF}')" +fi + +# Images per skin (svg files found recursively in `skins` folder and subfolders) +SKIN_IMAGES="" +if [ -d "${CURRENT_DIR}/skins" ]; then + SKIN_IMAGES="$(find "${CURRENT_DIR}/skins" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort | uniq)" +fi + +####################################################### + +# optimize svg +function optimize_svg() { + SOURCE="$1" + + cp ${SOURCE} ${SOURCE}.tmp + scour \ + --remove-descriptive-elements \ + --enable-id-stripping \ + --enable-viewboxing \ + --enable-comment-stripping \ + --nindent=4 \ + --quiet \ + -i ${SOURCE}.tmp \ + -o ${SOURCE} + rm ${SOURCE}.tmp +} + +# build icons +function build_image() { + SOURCE="$1" + TARGET="$2" + + echo "Building ${TARGET}" + + if [ ! -f "${SOURCE}" ]; then + echo "Missing file: ${SOURCE}" + exit 1 + fi + + optimize_svg "${SOURCE}" + + mkdir -p "$(dirname "${TARGET}")" + + inkscape \ + --export-width=${ICON_SIZE} \ + --export-height=${ICON_SIZE} \ + --export-filename=${TARGET} \ + "${SOURCE}" + + optipng ${OPTIPNG_OPTIONS} "${TARGET}" +} + +function build_image_for_skin() { + SKIN_CODE="$1" + + # skin images + for SKIN_IMAGE in ${SKIN_IMAGES} + do + build_image ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_IMAGE}.svg ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_IMAGE}.png + done +} + +####################################################### + +# Delete existing generated images +if [ -d "${ASSETS_DIR}/ui" ]; then + find ${ASSETS_DIR}/ui -type f -name "*.png" -delete +fi +if [ -d "${ASSETS_DIR}/skins" ]; then + find ${ASSETS_DIR}/skins -type f -name "*.png" -delete +fi + +# build game images +for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES} +do + build_image ${CURRENT_DIR}/images/${GAME_IMAGE}.svg ${ASSETS_DIR}/ui/${GAME_IMAGE}.png +done + +# build skins images +for SKIN in ${AVAILABLE_SKINS} +do + build_image_for_skin "${SKIN}" +done + diff --git a/icons/button_back.svg b/resources/ui/images/button_back.svg similarity index 100% rename from icons/button_back.svg rename to resources/ui/images/button_back.svg diff --git a/icons/button_delete_saved_game.svg b/resources/ui/images/button_delete_saved_game.svg similarity index 100% rename from icons/button_delete_saved_game.svg rename to resources/ui/images/button_delete_saved_game.svg diff --git a/icons/button_resume_game.svg b/resources/ui/images/button_resume_game.svg similarity index 100% rename from icons/button_resume_game.svg rename to resources/ui/images/button_resume_game.svg diff --git a/resources/ui/images/button_start.svg b/resources/ui/images/button_start.svg new file mode 100644 index 0000000000000000000000000000000000000000..e9d49d2172b9a0305db82779971e3c1e12f34a70 --- /dev/null +++ b/resources/ui/images/button_start.svg @@ -0,0 +1,2 @@ +<?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="m34.852 25.44c-1.1248-1.1302-4.0012-1.1302-4.0012 0v45.921c0 1.1316 2.8832 1.1316 4.0121 0l37.693-20.918c1.1289-1.1248 1.1479-2.9551 0.02171-4.084z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="8.257"/><path d="m36.382 28.754c-0.96243-0.96706-3.4236-0.96706-3.4236 0v39.292c0 0.96825 2.467 0.96825 3.4329 0l32.252-17.898c0.96594-0.96243 0.9822-2.5285 0.01858-3.4945z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.314"/></svg> diff --git a/icons/game_fail.svg b/resources/ui/images/game_fail.svg similarity index 100% rename from icons/game_fail.svg rename to resources/ui/images/game_fail.svg diff --git a/icons/game_win.svg b/resources/ui/images/game_win.svg similarity index 100% rename from icons/game_win.svg rename to resources/ui/images/game_win.svg diff --git a/icons/placeholder.svg b/resources/ui/images/placeholder.svg similarity index 100% rename from icons/placeholder.svg rename to resources/ui/images/placeholder.svg diff --git a/icons/skins/default/button_mark_mine_off.svg b/resources/ui/skins/default/button_mark_mine_off.svg similarity index 100% rename from icons/skins/default/button_mark_mine_off.svg rename to resources/ui/skins/default/button_mark_mine_off.svg diff --git a/icons/skins/default/button_mark_mine_on.svg b/resources/ui/skins/default/button_mark_mine_on.svg similarity index 100% rename from icons/skins/default/button_mark_mine_on.svg rename to resources/ui/skins/default/button_mark_mine_on.svg diff --git a/icons/skins/default/empty.svg b/resources/ui/skins/default/empty.svg similarity index 100% rename from icons/skins/default/empty.svg rename to resources/ui/skins/default/empty.svg diff --git a/icons/skins/default/indicator_report_off.svg b/resources/ui/skins/default/indicator_report_off.svg similarity index 100% rename from icons/skins/default/indicator_report_off.svg rename to resources/ui/skins/default/indicator_report_off.svg diff --git a/icons/skins/default/indicator_report_on.svg b/resources/ui/skins/default/indicator_report_on.svg similarity index 100% rename from icons/skins/default/indicator_report_on.svg rename to resources/ui/skins/default/indicator_report_on.svg diff --git a/icons/skins/default/indicator_walk_off.svg b/resources/ui/skins/default/indicator_walk_off.svg similarity index 100% rename from icons/skins/default/indicator_walk_off.svg rename to resources/ui/skins/default/indicator_walk_off.svg diff --git a/icons/skins/default/indicator_walk_on.svg b/resources/ui/skins/default/indicator_walk_on.svg similarity index 100% rename from icons/skins/default/indicator_walk_on.svg rename to resources/ui/skins/default/indicator_walk_on.svg diff --git a/icons/skins/default/tile_0.svg b/resources/ui/skins/default/tile_0.svg similarity index 100% rename from icons/skins/default/tile_0.svg rename to resources/ui/skins/default/tile_0.svg diff --git a/icons/skins/default/tile_1.svg b/resources/ui/skins/default/tile_1.svg similarity index 100% rename from icons/skins/default/tile_1.svg rename to resources/ui/skins/default/tile_1.svg diff --git a/icons/skins/default/tile_2.svg b/resources/ui/skins/default/tile_2.svg similarity index 100% rename from icons/skins/default/tile_2.svg rename to resources/ui/skins/default/tile_2.svg diff --git a/icons/skins/default/tile_3.svg b/resources/ui/skins/default/tile_3.svg similarity index 100% rename from icons/skins/default/tile_3.svg rename to resources/ui/skins/default/tile_3.svg diff --git a/icons/skins/default/tile_4.svg b/resources/ui/skins/default/tile_4.svg similarity index 100% rename from icons/skins/default/tile_4.svg rename to resources/ui/skins/default/tile_4.svg diff --git a/icons/skins/default/tile_5.svg b/resources/ui/skins/default/tile_5.svg similarity index 100% rename from icons/skins/default/tile_5.svg rename to resources/ui/skins/default/tile_5.svg diff --git a/icons/skins/default/tile_6.svg b/resources/ui/skins/default/tile_6.svg similarity index 100% rename from icons/skins/default/tile_6.svg rename to resources/ui/skins/default/tile_6.svg diff --git a/icons/skins/default/tile_7.svg b/resources/ui/skins/default/tile_7.svg similarity index 100% rename from icons/skins/default/tile_7.svg rename to resources/ui/skins/default/tile_7.svg diff --git a/icons/skins/default/tile_8.svg b/resources/ui/skins/default/tile_8.svg similarity index 100% rename from icons/skins/default/tile_8.svg rename to resources/ui/skins/default/tile_8.svg diff --git a/icons/skins/default/tile_flag.svg b/resources/ui/skins/default/tile_flag.svg similarity index 100% rename from icons/skins/default/tile_flag.svg rename to resources/ui/skins/default/tile_flag.svg diff --git a/icons/skins/default/tile_flag_ko.svg b/resources/ui/skins/default/tile_flag_ko.svg similarity index 100% rename from icons/skins/default/tile_flag_ko.svg rename to resources/ui/skins/default/tile_flag_ko.svg diff --git a/icons/skins/default/tile_flag_ok.svg b/resources/ui/skins/default/tile_flag_ok.svg similarity index 100% rename from icons/skins/default/tile_flag_ok.svg rename to resources/ui/skins/default/tile_flag_ok.svg diff --git a/icons/skins/default/tile_mine.svg b/resources/ui/skins/default/tile_mine.svg similarity index 100% rename from icons/skins/default/tile_mine.svg rename to resources/ui/skins/default/tile_mine.svg diff --git a/icons/skins/default/tile_mine_not_found.svg b/resources/ui/skins/default/tile_mine_not_found.svg similarity index 100% rename from icons/skins/default/tile_mine_not_found.svg rename to resources/ui/skins/default/tile_mine_not_found.svg diff --git a/icons/skins/default/tile_unknown.svg b/resources/ui/skins/default/tile_unknown.svg similarity index 100% rename from icons/skins/default/tile_unknown.svg rename to resources/ui/skins/default/tile_unknown.svg