From db163e64312e2c07374fa7058832ecc25ede4bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Sat, 11 May 2024 01:27:40 +0200 Subject: [PATCH] Improve game architecture --- android/app/build.gradle | 2 +- android/gradle.properties | 4 +- assets/icons/button_delete_saved_game.png | Bin 5813 -> 0 bytes assets/icons/button_resume_game.png | Bin 3659 -> 0 bytes assets/icons/game_fail.png | Bin 3647 -> 0 bytes assets/icons/layout_diamond.png | Bin 8682 -> 0 bytes assets/icons/layout_english.png | Bin 8028 -> 0 bytes assets/icons/layout_french.png | Bin 8964 -> 0 bytes assets/icons/layout_german.png | Bin 4358 -> 0 bytes assets/icons/skin_default.png | Bin 209 -> 0 bytes assets/translations/en.json | 2 - assets/translations/fr.json | 2 - .../metadata/android/en-US/changelogs/18.txt | 1 + .../metadata/android/fr-FR/changelogs/18.txt | 1 + icons/build_game_icons.sh | 30 --- icons/button_delete_saved_game.svg | 2 - icons/button_resume_game.svg | 2 - icons/game_fail.svg | 2 - icons/layout_diamond.svg | 2 - icons/layout_english.svg | 2 - icons/layout_french.svg | 2 - icons/layout_german.svg | 2 - icons/skin_default.svg | 2 - lib/config/default_game_settings.dart | 33 +++ lib/config/default_global_settings.dart | 27 ++ lib/config/menu.dart | 63 ++--- lib/cubit/game_cubit.dart | 127 ++++++++++ lib/cubit/game_state.dart | 19 ++ .../{bottom_nav_cubit.dart => nav_cubit.dart} | 22 +- lib/cubit/settings_game_cubit.dart | 64 +++++ lib/cubit/settings_game_state.dart | 19 ++ lib/cubit/settings_global_cubit.dart | 60 +++++ lib/cubit/settings_global_state.dart | 19 ++ lib/data/game_data.dart | 44 ++++ lib/entities/tile.dart | 82 ------ lib/main.dart | 48 ++-- lib/models/board.dart | 169 +++++++++++++ lib/models/cell.dart | 41 +++ lib/models/cell_location.dart | 34 +++ lib/models/game.dart | 142 +++++++++++ lib/models/settings_game.dart | 41 +++ lib/models/settings_global.dart | 41 +++ lib/models/types.dart | 0 lib/provider/data.dart | 239 ------------------ lib/ui/layout/game.dart | 36 --- lib/ui/layout/parameters.dart | 129 ---------- lib/ui/layout/tileset.dart | 42 --- lib/ui/painters/parameter_painter.dart | 190 ++++++++++++++ lib/ui/screens/game_page.dart | 85 ------- .../{about_page.dart => page_about.dart} | 6 +- lib/ui/screens/page_game.dart | 19 ++ ...{settings_page.dart => page_settings.dart} | 6 +- lib/ui/skeleton.dart | 37 +-- lib/ui/widgets/app_bar.dart | 37 --- lib/ui/widgets/bottom_nav_bar.dart | 36 --- lib/ui/widgets/button_game_start_new.dart | 34 +++ lib/ui/widgets/game/game_widget.dart | 38 +++ lib/ui/widgets/game/indicator_top.dart | 62 ++--- lib/ui/widgets/game/message_game_end.dart | 68 +++-- lib/ui/widgets/game/tile_widget.dart | 135 ++++++++++ lib/ui/widgets/game/tileset.dart | 45 ++++ lib/ui/widgets/global_app_bar.dart | 84 ++++++ .../app_header.dart} | 5 +- lib/ui/widgets/helpers/app_title.dart | 17 ++ lib/ui/widgets/home/button_game_restart.dart | 21 -- lib/ui/widgets/home/button_game_resume.dart | 42 --- .../widgets/home/button_game_start_new.dart | 38 --- lib/ui/widgets/parameters.dart | 115 +++++++++ lib/utils/board_utils.dart | 120 --------- lib/utils/game_utils.dart | 180 ------------- pubspec.lock | 92 +++---- pubspec.yaml | 16 +- 72 files changed, 1780 insertions(+), 1347 deletions(-) delete mode 100644 assets/icons/button_delete_saved_game.png delete mode 100644 assets/icons/button_resume_game.png delete mode 100644 assets/icons/game_fail.png delete mode 100644 assets/icons/layout_diamond.png delete mode 100644 assets/icons/layout_english.png delete mode 100644 assets/icons/layout_french.png delete mode 100644 assets/icons/layout_german.png delete mode 100644 assets/icons/skin_default.png create mode 100644 fastlane/metadata/android/en-US/changelogs/18.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/18.txt delete mode 100644 icons/button_delete_saved_game.svg delete mode 100644 icons/button_resume_game.svg delete mode 100644 icons/game_fail.svg delete mode 100644 icons/layout_diamond.svg delete mode 100644 icons/layout_english.svg delete mode 100644 icons/layout_french.svg delete mode 100644 icons/layout_german.svg delete mode 100644 icons/skin_default.svg create mode 100644 lib/config/default_game_settings.dart create mode 100644 lib/config/default_global_settings.dart create mode 100644 lib/cubit/game_cubit.dart create mode 100644 lib/cubit/game_state.dart rename lib/cubit/{bottom_nav_cubit.dart => nav_cubit.dart} (51%) create mode 100644 lib/cubit/settings_game_cubit.dart create mode 100644 lib/cubit/settings_game_state.dart create mode 100644 lib/cubit/settings_global_cubit.dart create mode 100644 lib/cubit/settings_global_state.dart create mode 100644 lib/data/game_data.dart delete mode 100644 lib/entities/tile.dart create mode 100644 lib/models/board.dart create mode 100644 lib/models/cell.dart create mode 100644 lib/models/cell_location.dart create mode 100644 lib/models/game.dart create mode 100644 lib/models/settings_game.dart create mode 100644 lib/models/settings_global.dart create mode 100644 lib/models/types.dart delete mode 100644 lib/provider/data.dart delete mode 100644 lib/ui/layout/game.dart delete mode 100644 lib/ui/layout/parameters.dart delete mode 100644 lib/ui/layout/tileset.dart create mode 100644 lib/ui/painters/parameter_painter.dart delete mode 100644 lib/ui/screens/game_page.dart rename lib/ui/screens/{about_page.dart => page_about.dart} (89%) create mode 100644 lib/ui/screens/page_game.dart rename lib/ui/screens/{settings_page.dart => page_settings.dart} (80%) delete mode 100644 lib/ui/widgets/app_bar.dart delete mode 100644 lib/ui/widgets/bottom_nav_bar.dart create mode 100644 lib/ui/widgets/button_game_start_new.dart create mode 100644 lib/ui/widgets/game/game_widget.dart create mode 100644 lib/ui/widgets/game/tile_widget.dart create mode 100644 lib/ui/widgets/game/tileset.dart create mode 100644 lib/ui/widgets/global_app_bar.dart rename lib/ui/widgets/{header_app.dart => helpers/app_header.dart} (77%) create mode 100644 lib/ui/widgets/helpers/app_title.dart delete mode 100644 lib/ui/widgets/home/button_game_restart.dart delete mode 100644 lib/ui/widgets/home/button_game_resume.dart delete mode 100644 lib/ui/widgets/home/button_game_start_new.dart create mode 100644 lib/ui/widgets/parameters.dart delete mode 100644 lib/utils/board_utils.dart delete mode 100644 lib/utils/game_utils.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 9e2643e..c3c8f66 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -37,7 +37,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 namespace "org.benoitharrault.solitaire" defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index cd2d833..30298b3 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.0.17 -app.versionCode=17 +app.versionName=0.0.18 +app.versionCode=18 diff --git a/assets/icons/button_delete_saved_game.png b/assets/icons/button_delete_saved_game.png deleted file mode 100644 index 5e4f217689b11e444b7163557d7e5d68f3bbfe7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5813 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 diff --git a/assets/icons/button_resume_game.png b/assets/icons/button_resume_game.png deleted file mode 100644 index b2ea0a02d05e42377eb551a4b51428b511a32f5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3659 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Bd2>3_*8t*cliYI14-?iy0W?oEaG8oERJS zYjH3zFi4iTMwA5Sr<If^7Ns(jmzV2h=4BTrCl;jY<rk&TerF@az`%RM)5S5Q;?~=_ zl@Xz-a>r-BocS|*o}0kdqnkW^Pp>%{w0TQOkm2T;ne!{<41Iz(osm7ec8!jDw2!VP ztEh{@bcdUc3W0{<B6`;1tf@QSe2;r@hqb==p7w_wjo)n-%z66z_;Y)G{(Uv}&-dN` zJn!>9J(nj;4PiS)?Z2E?;t!8}(R=9DvhQC)w~6jDx}C*Pk$5vvGAUBX*mQ$s^d{j4 zeMO7^PVFhm4w!Ur)-LVX3+pX2R?U_TbPxW1?NYr{_KV&Gh93|uzqF2q9N#o`#r7qe z)`VWWyg%aGE}sUrMfY|Yt*$RwH({g3t&~d51+(Jr@Eus&o_bdOn97l^o0lcm{7aI1 zzjH3r7a#r2?JRThH0nyzdH*Ra@UHYdldxdMQJ!Yoh%?K6uN8T~|10bEr42rc@(0<A zL=<g;PiU{mi7L`&DYz>6Ca_qkhgTv;A^n+)RO{uX+guDc-?egWuv+x5{aobb_`{*{ z8ZFn4GS%)lQMJu)L7lA7f+g2}h4{>yV9m)YuE8_yW`yDHq_+7}R2Vj^FFY{GSg@H> zgzI#k&$<cG-dCoFH|&?Q%eU;-whyq<XAcX`nrP~K=F40^?giT0Tlb4LS=tA%=LG8R zJtOvT3Il7X?`LtBhk5(~;@n4l#IKp0-8qwCo#0I;J~o~e6JN+E{=Rj4aX9CM%nf<T z@!}FX3*7FBEdM%_ZFhV$QwhKA?G?uz7)>`vUth8R)5Oj&#v-k^(g8J%w-P1K8{W7c zukPTq@2LtKM@Hilr_-9>a-vr=9CMTUnz`s^&q2nh2b)zEpS<Cq9L0KK-KO9Bgj!zm zUts$$ws~IahP)h6g{N~&IhmQaw9NQ<B#iM}I(MXm$O2WRqLwpaj8%V2*6ibHX4-OO znlP{Gn=%dIgddw&7K?&Jx+*5Sp7|H>?Z)RLJ14sFa9t4jytpSt?8M=vM|>vlJ^b20 z!y$OXnayu@i(J38{47T^gF`fk>&(EwsKC&`AR)rQ$k4#R;K0D3z`!8Dz`()4z{0@5 z#6XsC!?`D|^Yw!6w{l*&o2|ank8{ayyN(6v`%XXoXPLZx%B9Ki%Pua~?!VN~U$@wO zn*5^U;d2+HUw=CB_%$yUE~XQPe^jp|M`@JKmMIWjH<>x}#x0-ab;n-FcdtC(KJm$` zlXkoRw(an}>Buam-u-_Y$H69sPYd_^Un+f=zwr4Mfms{Y{?-28kzafw_Rq5n?H|`4 zS*bR#)z9&g`}f1Gfx)Pub&oEig!1**xBtAEaCP~uy|1>rpMSakLU8uE3oUO$Iiy%# zSjE_uZ_z%`k-zEooNpUe@0{`>{^Z{Gt)8!>8JJ2|GVtqrzbyE%{z>CbLz7?ZUzj-- z2ryQi=XxK)81?I5gX+mIht8*R$g@mX&yaKPot^29%d8oDlb>vDW~(onVzVtwp+&%< zj%ma1zvozft}8FQ@FO(v%dcB3Qu{1=s;XFvIuGQX=ll7{#P>$qA|6H&6$b8K%Wn7> zncjLK6sR5cTh(e>`hu0xb&rJ}=DoLl^nPNigA>C~?gNMFRhIu*_27QTi{PUsB^TGn zC~Y{p?Ya3pX9LBC8rF)}vzg6#|4+To@vqbJ%Z1+y&xxn{Z`9EDTeQ)By3zuWgWmX@ zERSjsJJZy=@6POJFL+{HZ*E-FvxtW=MWtcgj<)Cqu`cbiCdI*AI}6w7ZC>=}iFkUz z6vc*5tQikW5=Hm^an*?roVT;Rt#+w%21t0vCJp}2oR6ae=Lx%>|KG5Phw+KV0o6Lr zH;>wz_(eNhe~Lfv-t?pTd#r%_{||dR1e_UWau#^b;#i|mzuSIpm4Erai`n1!4lQ`v zp1Ef}V`3YFk+8!e4)N8mGuOpyl|HW!+Irmj;Fs^`{Vueu)i@>KkjK<v;whl`+DUis z&##Q@>I%fWj94ZtW(eBPE72*b`sI!LZ;S9Li(l;QZRN0FnJ|-Kx>eW|_L>JZf$yH} z{96?G?<G?cA7hHzfwwxR^4ZPApBAymF`e*X=v12Gp>gWXUpA3c4h4Tkm97c38?SvV zkh|aYDGwwR%xD!b#a;LIc~z(P-@jj6@s(A8f$4-#1E0v#z5C}cK3H7-Sv>VVH&~FN zQ|U^yp7_(>_nt-u@-1nUUz)K=!<k_xw?beG*J1~6rv8b)Z`Zi~+!%V?Wb1iNmVhn> zA=eXv-J7oeO4+x_#@X6cl1amtq0`mUQ|HuqPmNQ``_H#C2sorMm2A+kH!J5a50rZx z&BLhBaE9eY(;^$cR98u+6^;$-G)~>tQeXi2I;gwIb8gF`HGOCIC^Sg1PKcN?xpe85 zKSlc%*<?Hw<X~V-(P(fHym8ukjfTD5`Jao<d^2xl5OA2s<RP-~VAAsShtFGWojHBB z#j#|ODl;jT3m_N%@SP*}wc_`)-ErTe1^5{i8k!zVXs8vo*c^ADZ!t)Bsn7=Vi|^(O zt~sU0z_>++p`GJ@Ns{*Qe|DV$3`{G6TT&g5&%L!xkja4|)N6y;n`gEU{@oM=Nfk>y z*{S#PS$UJ#F=qyj1?QCBoLN1sziti}qe8>4yyjy|V+*oeE3Q=8vNSNfisD#p7M*dT zX|LZ~?^EZ(+ZY5KOdsm6GP%6Nf6<&>zxd-<ANDd}32^^#^XGA!_FuLZ`qSd>w6#A} zXgJiY|1{!y^`82rAOD@QUBJxrpmoNzyHDM8IG<OEFL?jQLTdX`p7+cS45|BsPFgHo zzr=I3rT>~QVaIPL2srpM6|ntxy82XhS>-vMcb2+WJ|37o>zqy-gO~UNG4VU6(|Jtf zk3C<%@9+<Wh9K4lJo}E`I}%%PvvAE{aoGhMXM1ln6k`c^cC90bJ8wR}vT31$IZMFH zXFGZ(CmpPP`B~x|v&px61wIN4EHA7WEt=zFGTjcgGH3}nBprNfI(7epsvn^lT0aCF z&M|rD?D(6$@PQKpuk57jK?U>UdYZTyr|2}aJeaSs;(^|d@8`G{r|&!M`a!Xwhh;)Q z!QNW_&j0$)o9`FzGUL``nXs8*&cXF7w_i|KRNu+{Zn1X%r1pB%Lwt-WN(bI*><DJc zIPfT?>fv*v78#}!E)1NX|GWr3>bJMAqV}L|e}OZ@Ob&&O5B#3!?AV*$dU^BffC9ya zIV=-C@U45&v|E-{oauxs1E;lqfq0C*NDPO9IirPOj_(%3DEIo$e2xcO8Ey$Kcq*|- z{n=;HJM7PoUwR?mEf~YGAcnE;dH99y`L5m?J1RMJSYA{y@}*7l;;M7pd9hGL)BoVx zukZ9tN_=-EeZSMlpe1rZ{Xuyq>veCZgLk`{xIx+Zz|Gw&4rcAo{2-lMc1vLCV)isP z>-Yj&=FA6|noY`#x%5~jtYqlfDDNKRSz5cB-OoGnpzL4eumZ&fCYAyjc9poB>G^g( zj_jW`KcxufcAZ^P`ER93(Mvm#7>)%wjK^{{pL1;#sq($=didY;#wKpYC+ZB`OQqd2 zpB}uMt#148W#OkAVT}w<3^TbD4sHAM?#zSTzMM<ir!Ui}Gmhj~V6dw@{LVL)jO>55 zpMKXIfBAf_VBNOvPcwWY4z@DHI?U%^e2ZuQ>~w+N&)WH)H~$rreG%}XdjJ0Oib73n zjJtTm4s2WI*c>L4`F*q8KhZ1YjxzNJYs<xXT@SW06g_;oe)EdsTQ00#_osQ9eBnFE zD#Ihq;c}%}R#)z7cgRJ~dnCaAu!&()lWmwm(cXK8&(pVCR@&Pi+de}sy7Y|U+Lwm9 zM?OXvasRn%T-F`DltpTjRqwIik^)*3H4dOHjD|k0fTU$7?>!V?$l#t<IjwfORioOu zUX!GNhGjG4Q&l!QJdW+i_|a9dnH8yhqvmk_>~U!?Lk$P-A9FJ_1UKKG%lTlFe)Ne$ z3m9|-C*GZLa?^W(2Nx`}mDpG_I-Q@dWshP#v1-GweL^A4jf}G%gc`oKocfJ-Et7<F z=9A@~YQk*n8I9*B`!3tXR<$dX!42GSW6uCJ+)hrrvz==~?uOd`VI0dG9qw-ENta&p zdY!Pt5#292i{+YGuAGsJO>Ue~`dymy!KBm;zxQ|MGcI_vx_;TgeNRuFiC1knF;~zs zLYU!|irM)|s%d+otC@C)Tr)WvDU!VEcmt#9L*Z<b*V=10uRr^{l*z#8^xOT51emve za4X<VopSTreT~=VA`QN+LEURI_1Tj?G;B(gv@5z-6?NuId<9Fv%FHEpA6ciZ>132} zS>U7Ab>s=>&aF(gH^R={7HJ3!%9a(HuKvWQiN&EY{NIln#p40a{#&MhEMrZ$!g=NW zhsx78uQy-hY$$s6=aI-ipX%_s<*XUYqTP1tX2mU8$;ix;@^9Jnf@N344p*%)zF8K= z!2a{9uie8)C%JvR4R;b|-kGoZ<XO0>?vs-y(;3e0NS_j2{>~#?qbgV3g&|h{&jRCK z*Ih-g`Mz23=g(WpapBNitL9Q~pLG-VSzJ?DpwWDr_4KVv7Qx9e`zGA`lW{mUsQ8W` zW7LktYwp>3hUfA7T#D{{<CTA<zVt%O>9Scfc5R>bPde(chAP8{Lz@m89oe+S>}U+A z_3f{>MNVw}w?@&g-=-gV^LOLopKo~>*FE)DV%Cy=vwq(C+*vEvZ(4g{>di3b1Kg+i uP79o#G-GA^tj$+fR_+yLVpt&YkKfy1c8Nh;O*R7q1B0ilpUXO@geCyfY-%<D diff --git a/assets/icons/game_fail.png b/assets/icons/game_fail.png deleted file mode 100644 index 93f2801f9d6bb2ce508e1293cd64d6ff2e9970ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3647 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 a*}uy$dF>W!zRtkFz~JfX=d#Wzp$P!#%#zLk diff --git a/assets/icons/layout_diamond.png b/assets/icons/layout_diamond.png deleted file mode 100644 index d20c4ea546b070361e0d0d3aeb6682bca69273a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8682 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 diff --git a/assets/icons/layout_english.png b/assets/icons/layout_english.png deleted file mode 100644 index 3ca9c27286c0fc3ec7d3808cd23118965b95892d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8028 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 W|LvreHnt253=E#GelF{r5}E*;4ztMs diff --git a/assets/icons/layout_french.png b/assets/icons/layout_french.png deleted file mode 100644 index 71837c08f529075e14fce4db01f166cf9ac36d6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8964 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 diff --git a/assets/icons/layout_german.png b/assets/icons/layout_german.png deleted file mode 100644 index 37c51ca8fdb173b33d06b23435a806fdb215b9bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4358 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 diff --git a/assets/icons/skin_default.png b/assets/icons/skin_default.png deleted file mode 100644 index fbe9cb151a27e54b848f7cd48e397cd308f51dad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 209 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6Ow0@n4Emc|rZ6xta29w(7BetNp8{b<xs?;d z85kHOOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zA;0f>vab;j&;9}#^ zku=_ybuWLT*gFOW1~*R^$B>N1x3>&=859Iq4zj0uIP<GH9xjzPy4~Nr_SJQZz}3%s iZ!zEkADBDbXV=+Uv~<5Jn~-e-Qs(LE=d#Wzp$Py8rZ`0a diff --git a/assets/translations/en.json b/assets/translations/en.json index edfb95a..13da5fc 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,8 +1,6 @@ { "app_name": "Solitaire", - "long_press_to_quit": "Long press to quit game...", - "bottom_nav_home": "Game", "bottom_nav_settings": "Settings", "bottom_nav_about": "About", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index a4e231e..0d43876 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,8 +1,6 @@ { "app_name": "Solitaire", - "long_press_to_quit": "Appuyer longtemps pour quitter le jeu...", - "bottom_nav_home": "Jeu", "bottom_nav_settings": "Réglages", "bottom_nav_about": "Infos", diff --git a/fastlane/metadata/android/en-US/changelogs/18.txt b/fastlane/metadata/android/en-US/changelogs/18.txt new file mode 100644 index 0000000..9bb4c4d --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/18.txt @@ -0,0 +1 @@ +Improve application architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/18.txt b/fastlane/metadata/android/fr-FR/changelogs/18.txt new file mode 100644 index 0000000..5face79 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/18.txt @@ -0,0 +1 @@ +Amélioration de l'architecture de l'application. diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh index 4a9ca31..2191261 100755 --- a/icons/build_game_icons.sh +++ b/icons/build_game_icons.sh @@ -18,18 +18,10 @@ ICON_SIZE=192 AVAILABLE_GAME_IMAGES=" button_back button_start - button_resume_game - button_delete_saved_game - game_fail game_win placeholder " -# Settings images -AVAILABLES_GAME_SETTINGS=" - layout:french,german,english,diamond -" - # Skins AVAILABLE_SKINS=" default @@ -84,25 +76,9 @@ function build_icon() { optipng ${OPTIPNG_OPTIONS} ${TARGET} } -function build_settings_icons() { - INPUT_STRING="$1" - - SETTING_NAME="$(echo "${INPUT_STRING}" | cut -d":" -f1)" - SETTING_VALUES="$(echo "${INPUT_STRING}" | cut -d":" -f2 | tr "," " ")" - - for SETTING_VALUE in ${SETTING_VALUES} - do - SETTING_CODE="${SETTING_NAME}_${SETTING_VALUE}" - build_icon ${CURRENT_DIR}/${SETTING_CODE}.svg ${ASSETS_DIR}/icons/${SETTING_CODE}.png - done -} - function build_icon_for_skin() { SKIN_CODE="$1" - # skin main image - build_icon ${CURRENT_DIR}/skin_${SKIN_CODE}.svg ${ASSETS_DIR}/icons/skin_${SKIN_CODE}.png - # skin images for SKIN_IMAGE in ${SKIN_IMAGES} do @@ -126,12 +102,6 @@ do build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png done -# build settings images -for GAME_SETTING in ${AVAILABLES_GAME_SETTINGS} -do - build_settings_icons "${GAME_SETTING}" -done - # build skins images for SKIN in ${AVAILABLE_SKINS} do diff --git a/icons/button_delete_saved_game.svg b/icons/button_delete_saved_game.svg deleted file mode 100644 index ac7eefe..0000000 --- a/icons/button_delete_saved_game.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#ee7d49" stroke="#fff" stroke-width=".238"/><path d="m61.07 35.601-1.7399 27.837c-0.13442 2.1535-1.9205 3.8312-4.0781 3.8312h-16.84c-2.1576 0-3.9437-1.6777-4.0781-3.8312l-1.7399-27.837h-2.6176c-0.84621 0-1.5323-0.68613-1.5323-1.5323 0-0.84655 0.68613-1.5323 1.5323-1.5323h33.711c0.84621 0 1.5323 0.68578 1.5323 1.5323 0 0.84621-0.68613 1.5323-1.5323 1.5323zm-3.2617 0h-21.953l1.4715 26.674c0.05985 1.0829 0.95531 1.9305 2.0403 1.9305h14.929c1.085 0 1.9804-0.84757 2.0403-1.9305zm-10.977 3.0647c0.78977 0 1.4301 0.6403 1.4301 1.4301v19.614c0 0.78977-0.6403 1.4301-1.4301 1.4301s-1.4301-0.6403-1.4301-1.4301v-19.614c0-0.78977 0.6403-1.4301 1.4301-1.4301zm-6.1293 0c0.80004 0 1.4588 0.62935 1.495 1.4286l0.89647 19.719c0.03182 0.70016-0.50998 1.2933-1.2101 1.3255-0.01915 7.02e-4 -0.03831 1e-3 -0.05781 1e-3 -0.74462 0-1.3596-0.58215-1.4003-1.3261l-1.0757-19.719c-0.0407-0.74701 0.53188-1.3852 1.2786-1.4259 0.02462-0.0014 0.04926-2e-3 0.07388-2e-3zm12.259 0c0.74804 0 1.3541 0.60609 1.3541 1.3541 0 0.02462-3.28e-4 0.04926-0.0017 0.07388l-1.0703 19.618c-0.04379 0.80106-0.70597 1.4281-1.5081 1.4281-0.74804 0-1.3541-0.60609-1.3541-1.3541 0-0.02462 3.49e-4 -0.04925 0.0017-0.07388l1.0703-19.618c0.04379-0.80106 0.70597-1.4281 1.5081-1.4281zm-10.216-12.259h8.1728c2.2567 0 4.086 1.8293 4.086 4.086v2.0433h-16.344v-2.0433c0-2.2567 1.8293-4.086 4.086-4.086zm0.20453 3.0647c-0.67725 0-1.2259 0.54863-1.2259 1.2259v1.8388h10.215v-1.8388c0-0.67725-0.54863-1.2259-1.2259-1.2259z" fill="#fff" fill-rule="evenodd" stroke="#bd4812" stroke-width=".75383"/></svg> diff --git a/icons/button_resume_game.svg b/icons/button_resume_game.svg deleted file mode 100644 index 6ad8b64..0000000 --- a/icons/button_resume_game.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m39.211 31.236c-0.84086-0.84489-2.9911-0.84489-2.9911 0v34.329c0 0.84594 2.1554 0.84594 2.9993 0l28.178-15.637c0.84392-0.84086 0.85812-2.2091 0.01623-3.053z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="6.1726"/><path d="m40.355 33.714c-0.71948-0.72294-2.5594-0.72294-2.5594 0v29.373c0 0.72383 1.8442 0.72383 2.5663 0l24.11-13.38c0.7221-0.71948 0.73426-1.8902 0.01389-2.6124z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.225"/><path d="m28.369 66.919v-37.591" fill="#105ca2" stroke="#105ca2" stroke-linecap="round" stroke-width="4.0337"/></svg> diff --git a/icons/game_fail.svg b/icons/game_fail.svg deleted file mode 100644 index 2922fd7..0000000 --- a/icons/game_fail.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#d11717" stroke="#fff" stroke-width=".238"/><path d="m71.624 59.304c3.5089 3.5089 3.5089 9.0561 0 12.565-1.6976 1.6976-3.9623 2.6034-6.2261 2.6034s-4.5275-0.90569-6.2261-2.6034l-12.452-12.452-12.452 12.452c-1.6976 1.6976-3.9623 2.6034-6.2261 2.6034s-4.5275-0.90569-6.2261-2.6034c-3.5089-3.5089-3.5089-9.0561 0-12.565l12.452-12.452-12.452-12.452c-3.5089-3.5089-3.5089-9.0561 0-12.565s9.0561-3.5089 12.565 0l12.452 12.452 12.452-12.452c3.5089-3.5089 9.0561-3.5089 12.565 0s3.5089 9.0561 0 12.565l-12.452 12.452z" fill="#e7e7e7" stroke-width=".20213"/></svg> diff --git a/icons/layout_diamond.svg b/icons/layout_diamond.svg deleted file mode 100644 index 31d08b4..0000000 --- a/icons/layout_diamond.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6ade70" stroke="#000" stroke-width="2"/><g transform="matrix(.8379 0 0 .8379 -396.44 -4.3017)"><g transform="translate(180 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(228 96)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(228 3.624e-5)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(228 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(228 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 60)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 60)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 60)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(192 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(192 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(192 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(204 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(204 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(204 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 48)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(276 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><rect x="528" y="60" width="12" height="12" fill="#babdb6" stroke="#000"/><g transform="translate(240 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 48)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(240 12)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g></g></svg> diff --git a/icons/layout_english.svg b/icons/layout_english.svg deleted file mode 100644 index c819ca6..0000000 --- a/icons/layout_english.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6ade70" stroke="#000" stroke-width="2"/><g transform="translate(-363 -3)"><g transform="translate(120 48)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 48)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 48)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(72 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(72 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(72 24)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(84 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(84 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(84 24)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 36)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(156 24)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><rect x="408" y="48" width="12" height="12" fill="#babdb6" stroke="#000"/><g transform="translate(120 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(120 36)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(86.461 -21.272)"><g transform="translate(33.539 21.272)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(33.539 21.272)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g></g></g></svg> diff --git a/icons/layout_french.svg b/icons/layout_french.svg deleted file mode 100644 index 1ff5efe..0000000 --- a/icons/layout_french.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6ade70" stroke="#000" stroke-width="2"/><g transform="translate(-3 -15)"><g transform="translate(-204 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-252 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-252 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 24)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 60)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 60)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 60)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-288 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-288 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-288 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-276 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-276 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-276 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 48)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 48)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 48)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 48)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-204 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="288" y="24" width="12" height="12" fill="#babdb6" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 48)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-240 12)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><rect x="48" y="48" width="12" height="12" fill="#fff" stroke="#000"/><rect x="48" y="60" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 -100.3 35.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/><g transform="translate(-240 12)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g></g></svg> diff --git a/icons/layout_german.svg b/icons/layout_german.svg deleted file mode 100644 index e826669..0000000 --- a/icons/layout_german.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6ade70" stroke="#000" stroke-width="2"/><g transform="matrix(.84066 0 0 .84066 -95.275 -4.4834)"><g transform="translate(-168 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-168 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-168 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 72)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 3.0518e-5)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-156 36)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><rect x="168" y="60" width="12" height="12" fill="#babdb6" stroke="#000"/><g transform="translate(-120 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-120 36)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="276" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="288" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="300" y="24" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -.3088)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="276" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="288" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="300" y="12" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 -12.309)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="276" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 127.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="288" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 139.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g><g transform="translate(-84 36)"><rect x="300" y="36" width="12" height="12" fill="#fff" stroke="#000"/><circle transform="matrix(.5381 0 0 .5381 151.7 11.691)" cx="286.74" cy="56.325" r="7.4335" fill="#c17d11"/></g></g></svg> diff --git a/icons/skin_default.svg b/icons/skin_default.svg deleted file mode 100644 index f7344d5..0000000 --- a/icons/skin_default.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#be6ade" stroke="#000" stroke-width="2"/></svg> diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart new file mode 100644 index 0000000..6ac27dc --- /dev/null +++ b/lib/config/default_game_settings.dart @@ -0,0 +1,33 @@ +import 'package:solitaire/utils/tools.dart'; + +class DefaultGameSettings { + // available game parameters codes + static const String parameterCodeLayout = 'layout'; + static const List<String> availableParameters = [ + parameterCodeLayout, + ]; + + // layout: available values + static const String layoutValueFrench = 'french'; + static const String layoutValueGerman = 'german'; + static const String layoutValueEnglish = 'english'; + static const String layoutValueDiamond = 'diamond'; + static const List<String> allowedLayoutValues = [ + layoutValueFrench, + layoutValueGerman, + layoutValueEnglish, + layoutValueDiamond, + ]; + // layout: default value + static const String defaultLayoutValue = layoutValueEnglish; + + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeLayout: + return DefaultGameSettings.allowedLayoutValues; + } + + printlog('Did not find any available value for game parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000..622a8b6 --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,27 @@ +import 'package:solitaire/utils/tools.dart'; + +class DefaultGlobalSettings { + // available global parameters codes + static const String parameterCodeSkin = 'skin'; + static const List<String> availableParameters = [ + parameterCodeSkin, + ]; + + // skin: available values + static const String skinValueDefault = 'default'; + static const List<String> allowedSkinValues = [ + skinValueDefault, + ]; + // skin: default value + static const String defaultSkinValue = skinValueDefault; + + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeSkin: + return DefaultGlobalSettings.allowedSkinValues; + } + + printlog('Did not find any available value for global parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/menu.dart b/lib/config/menu.dart index ca5d227..3d2fcfa 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -1,10 +1,9 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:unicons/unicons.dart'; -import 'package:solitaire/ui/screens/about_page.dart'; -import 'package:solitaire/ui/screens/game_page.dart'; -import 'package:solitaire/ui/screens/settings_page.dart'; +import 'package:solitaire/ui/screens/page_about.dart'; +import 'package:solitaire/ui/screens/page_game.dart'; +import 'package:solitaire/ui/screens/page_settings.dart'; class MenuItem { final String code; @@ -19,35 +18,39 @@ class MenuItem { } class Menu { - static List<MenuItem> items = [ - const MenuItem( - code: 'bottom_nav_home', - icon: Icon(UniconsLine.home), - page: GamePage(), - ), - const MenuItem( - code: 'bottom_nav_settings', - icon: Icon(UniconsLine.setting), - page: SettingsPage(), - ), - const MenuItem( - code: 'bottom_nav_about', - icon: Icon(UniconsLine.info_circle), - page: AboutPage(), - ), - ]; + static const indexGame = 0; + static const menuItemGame = MenuItem( + code: 'bottom_nav_game', + icon: Icon(UniconsLine.home), + page: PageGame(), + ); - static Widget getPageWidget(int pageIndex) { - return Menu.items.elementAt(pageIndex).page; + static const indexSettings = 1; + static const menuItemSettings = MenuItem( + code: 'bottom_nav_settings', + icon: Icon(UniconsLine.setting), + page: PageSettings(), + ); + + static const indexAbout = 2; + static const menuItemAbout = MenuItem( + code: 'bottom_nav_about', + icon: Icon(UniconsLine.info_circle), + page: PageAbout(), + ); + + static Map<int, MenuItem> items = { + indexGame: menuItemGame, + indexSettings: menuItemSettings, + indexAbout: menuItemAbout, + }; + + static bool isIndexAllowed(int pageIndex) { + return items.keys.contains(pageIndex); } - static List<BottomNavigationBarItem> getMenuItems() { - return Menu.items - .map((MenuItem item) => BottomNavigationBarItem( - icon: item.icon, - label: tr(item.code), - )) - .toList(); + static Widget getPageWidget(int pageIndex) { + return items[pageIndex]?.page ?? menuItemGame.page; } static int itemsCount = Menu.items.length; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000..aba2472 --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,127 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:solitaire/models/cell_location.dart'; +import 'package:solitaire/models/game.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/models/settings_global.dart'; +import 'package:solitaire/utils/tools.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() + : super(GameState( + currentGame: Game.createNull(), + )); + + void updateState(Game game) { + emit(GameState( + currentGame: game, + )); + } + + void refresh() { + final Game game = Game( + gameSettings: state.currentGame.gameSettings, + globalSettings: state.currentGame.globalSettings, + board: state.currentGame.board, + isRunning: state.currentGame.isRunning, + isFinished: state.currentGame.isFinished, + movesCount: state.currentGame.movesCount, + remainingPegsCount: state.currentGame.remainingPegsCount, + allowedMovesCount: state.currentGame.allowedMovesCount, + ); + // game.dump(); + + updateState(game); + } + + void startNewGame({ + required GameSettings gameSettings, + required GlobalSettings globalSettings, + }) { + Game newGame = Game.createNew( + gameSettings: gameSettings, + globalSettings: globalSettings, + ); + + newGame.dump(); + + updateState(newGame); + + updateRemainingPegsCount(newGame.countRemainingPegs()); + updateAllowedMovesCount(newGame.countAllowedMoves()); + + refresh(); + } + + void quitGame() { + state.currentGame.isRunning = false; + refresh(); + } + + void updatePegValue(CellLocation location, bool hasPeg) { + state.currentGame.board.cells[location.row][location.col].hasPeg = hasPeg; + refresh(); + } + + void incrementMovesCount() { + state.currentGame.movesCount++; + refresh(); + } + + void updateRemainingPegsCount(int count) { + state.currentGame.remainingPegsCount = count; + refresh(); + } + + void updateAllowedMovesCount(int count) { + state.currentGame.allowedMovesCount = count; + if (count == 0) { + state.currentGame.isFinished = true; + } + + refresh(); + } + + void move({ + required Game currentGame, + required List<int> source, + required List<int> target, + }) { + printlog('Move from $source to $target'); + final int sourceCol = source[0]; + final int sourceRow = source[1]; + final int targetCol = target[0]; + final int targetRow = target[1]; + + final int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round(); + final int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round(); + + updatePegValue(CellLocation.go(sourceRow, sourceCol), false); + updatePegValue(CellLocation.go(targetRow, targetCol), true); + updatePegValue(CellLocation.go(middleRow, middleCol), false); + + incrementMovesCount(); + updateRemainingPegsCount(currentGame.countRemainingPegs()); + updateAllowedMovesCount(currentGame.countAllowedMoves()); + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + Game currentGame = json['currentGame'] as Game; + + return GameState( + currentGame: currentGame, + ); + } + + @override + Map<String, dynamic>? toJson(GameState state) { + return <String, dynamic>{ + 'currentGame': state.currentGame.toJson(), + }; + } +} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart new file mode 100644 index 0000000..3fd161a --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,19 @@ +part of 'game_cubit.dart'; + +@immutable +class GameState extends Equatable { + const GameState({ + required this.currentGame, + }); + + final Game currentGame; + + @override + List<dynamic> get props => <dynamic>[ + currentGame, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'currentGame': currentGame, + }; +} diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/nav_cubit.dart similarity index 51% rename from lib/cubit/bottom_nav_cubit.dart rename to lib/cubit/nav_cubit.dart index 8f04dff..9d4541c 100644 --- a/lib/cubit/bottom_nav_cubit.dart +++ b/lib/cubit/nav_cubit.dart @@ -2,26 +2,32 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:solitaire/config/menu.dart'; -class BottomNavCubit extends HydratedCubit<int> { - BottomNavCubit() : super(0); +class NavCubit extends HydratedCubit<int> { + NavCubit() : super(0); void updateIndex(int index) { - if (isIndexAllowed(index)) { + if (Menu.isIndexAllowed(index)) { emit(index); } else { - goToHomePage(); + goToGamePage(); } } - bool isIndexAllowed(int index) { - return (index >= 0) && (index < Menu.itemsCount); + void goToGamePage() { + emit(Menu.indexGame); } - void goToHomePage() => emit(0); + void goToSettingsPage() { + emit(Menu.indexSettings); + } + + void goToAboutPage() { + emit(Menu.indexAbout); + } @override int fromJson(Map<String, dynamic> json) { - return 0; + return Menu.indexGame; } @override diff --git a/lib/cubit/settings_game_cubit.dart b/lib/cubit/settings_game_cubit.dart new file mode 100644 index 0000000..7399314 --- /dev/null +++ b/lib/cubit/settings_game_cubit.dart @@ -0,0 +1,64 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:solitaire/config/default_game_settings.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/utils/tools.dart'; + +part 'settings_game_state.dart'; + +class GameSettingsCubit extends HydratedCubit<GameSettingsState> { + GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault())); + + void setValues({ + String? layout, + }) { + emit( + GameSettingsState( + settings: GameSettings( + layout: layout ?? state.settings.layout, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGameSettings.parameterCodeLayout: + return GameSettings.getLayoutValueFromUnsafe(state.settings.layout); + } + return ''; + } + + void setParameterValue(String code, String value) { + printlog('GameSettingsCubit.setParameterValue'); + printlog('code: $code / value: $value'); + + String layout = code == DefaultGameSettings.parameterCodeLayout + ? value + : getParameterValue(DefaultGameSettings.parameterCodeLayout); + + setValues( + layout: layout, + ); + } + + @override + GameSettingsState? fromJson(Map<String, dynamic> json) { + String layout = json[DefaultGameSettings.parameterCodeLayout] as String; + + return GameSettingsState( + settings: GameSettings( + layout: layout, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GameSettingsState state) { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeLayout: state.settings.layout, + }; + } +} diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart new file mode 100644 index 0000000..b773dc6 --- /dev/null +++ b/lib/cubit/settings_game_state.dart @@ -0,0 +1,19 @@ +part of 'settings_game_cubit.dart'; + +@immutable +class GameSettingsState extends Equatable { + const GameSettingsState({ + required this.settings, + }); + + final GameSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000..274c113 --- /dev/null +++ b/lib/cubit/settings_global_cubit.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:solitaire/config/default_global_settings.dart'; +import 'package:solitaire/models/settings_global.dart'; + +part 'settings_global_state.dart'; + +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault())); + + void setValues({ + String? skin, + }) { + emit( + GlobalSettingsState( + settings: GlobalSettings( + skin: skin ?? state.settings.skin, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGlobalSettings.parameterCodeSkin: + return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String skin = (code == DefaultGlobalSettings.parameterCodeSkin) + ? value + : getParameterValue(DefaultGlobalSettings.parameterCodeSkin); + + setValues( + skin: skin, + ); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String; + + return GlobalSettingsState( + settings: GlobalSettings( + skin: skin, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GlobalSettingsState state) { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: state.settings.skin, + }; + } +} diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart new file mode 100644 index 0000000..4e4fbdf --- /dev/null +++ b/lib/cubit/settings_global_state.dart @@ -0,0 +1,19 @@ +part of 'settings_global_cubit.dart'; + +@immutable +class GlobalSettingsState extends Equatable { + const GlobalSettingsState({ + required this.settings, + }); + + final GlobalSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/data/game_data.dart b/lib/data/game_data.dart new file mode 100644 index 0000000..711256b --- /dev/null +++ b/lib/data/game_data.dart @@ -0,0 +1,44 @@ +class GameData { + static const Map<String, List<String>> templates = { + 'french': [ + ' ooo ', + ' ooooo ', + 'ooo·ooo', + 'ooooooo', + 'ooooooo', + ' ooooo ', + ' ooo ', + ], + 'german': [ + ' ooo ', + ' ooo ', + ' ooo ', + 'ooooooooo', + 'oooo·oooo', + 'ooooooooo', + ' ooo ', + ' ooo ', + ' ooo ', + ], + 'english': [ + ' ooo ', + ' ooo ', + 'ooooooo', + 'ooo·ooo', + 'ooooooo', + ' ooo ', + ' ooo ', + ], + 'diamond': [ + ' o ', + ' ooo ', + ' ooooo ', + ' ooooooo ', + 'oooo·oooo', + ' ooooooo ', + ' ooooo ', + ' ooo ', + ' o ', + ] + }; +} diff --git a/lib/entities/tile.dart b/lib/entities/tile.dart deleted file mode 100644 index 5f88b97..0000000 --- a/lib/entities/tile.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class Tile { - int currentRow; - int currentCol; - bool hasPeg; - - Tile( - this.currentRow, - this.currentCol, - this.hasPeg, - ); - - Widget render(Data myProvider) { - List<Widget> stack = [ - hole(myProvider), - ]; - - if (hasPeg) { - stack.add(draggable(myProvider)); - } - - return Stack( - alignment: Alignment.center, - children: stack, - ); - } - - Widget hole(Data myProvider) { - String image = 'assets/skins/${myProvider.parameterSkin}_hole.png'; - - return DragTarget<List<int>>( - builder: ( - BuildContext context, - List<dynamic> accepted, - List<dynamic> rejected, - ) { - return Image( - image: AssetImage(image), - width: myProvider.tileSize, - height: myProvider.tileSize, - fit: BoxFit.fill, - ); - }, - onAcceptWithDetails: (DragTargetDetails<List<int>> source) { - List<int> target = [currentCol, currentRow]; - // printlog('(drag) Pick from ' + source.toString() + ' and drop on ' + target.toString()); - if (GameUtils.isMoveAllowed(myProvider, source.data, target)) { - GameUtils.move(myProvider, source.data, target); - } - }, - ); - } - - Widget draggable(Data myProvider) { - return Draggable<List<int>>( - data: [currentCol, currentRow], - - // Widget when draggable is being dragged - feedback: peg(myProvider), - - // Widget to display on original place when being dragged - childWhenDragging: Container(), - - // Widget when draggable is stationary - child: peg(myProvider), - ); - } - - Widget peg(Data myProvider) { - String image = 'assets/skins/${myProvider.parameterSkin}_peg.png'; - - return Image( - image: AssetImage(image), - width: myProvider.tileSize, - height: myProvider.tileSize, - fit: BoxFit.fill, - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index e84151f..d507c2d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,17 +6,17 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hive/hive.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:provider/provider.dart'; -import 'package:overlay_support/overlay_support.dart'; import 'package:solitaire/config/theme.dart'; -import 'package:solitaire/cubit/bottom_nav_cubit.dart'; +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/cubit/nav_cubit.dart'; +import 'package:solitaire/cubit/settings_game_cubit.dart'; +import 'package:solitaire/cubit/settings_global_cubit.dart'; import 'package:solitaire/cubit/theme_cubit.dart'; -import 'package:solitaire/provider/data.dart'; import 'package:solitaire/ui/skeleton.dart'; void main() async { - /// Initialize packages + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); final Directory tmpDir = await getTemporaryDirectory(); @@ -46,34 +46,28 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), + BlocProvider<NavCubit>(create: (context) => NavCubit()), BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()), + BlocProvider<GameCubit>(create: (context) => GameCubit()), + BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()), + BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()), ], child: BlocBuilder<ThemeCubit, ThemeModeState>( builder: (BuildContext context, ThemeModeState state) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>( - builder: (context, data, child) { - return OverlaySupport( - child: MaterialApp( - title: 'Solitaire', - home: const SkeletonScreen(), + return MaterialApp( + title: 'Solitaire', + home: const SkeletonScreen(), - // Theme stuff - theme: lightTheme, - darkTheme: darkTheme, - themeMode: state.themeMode, + // Theme stuff + theme: lightTheme, + darkTheme: darkTheme, + themeMode: state.themeMode, - // Localization stuff - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - ), - ); - }, - ), + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, ); }, ), diff --git a/lib/models/board.dart b/lib/models/board.dart new file mode 100644 index 0000000..eb1ce73 --- /dev/null +++ b/lib/models/board.dart @@ -0,0 +1,169 @@ +import 'package:solitaire/data/game_data.dart'; +import 'package:solitaire/models/cell.dart'; +import 'package:solitaire/models/cell_location.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/utils/tools.dart'; + +typedef BoardCells = List<List<Cell>>; + +class Board { + Board({ + required this.cells, + }); + + BoardCells cells = const []; + + factory Board.createEmpty() { + return Board( + cells: [], + ); + } + + factory Board.createNew({ + required GameSettings gameSettings, + }) { + final List<String>? template = GameData.templates[gameSettings.layout]; + + final BoardCells grid = []; + + int row = 0; + template?.forEach((String line) { + final List<Cell> gridLine = []; + int col = 0; + line.split("").forEach((String tileCode) { + gridLine.add(tileCode == ' ' + ? Cell.none + : Cell( + location: CellLocation.go(row, col), + hasHole: true, + hasPeg: (tileCode == 'o'), + )); + col++; + }); + row++; + grid.add(gridLine); + }); + + return Board( + cells: grid, + ); + } + + int get boardSize => cells.length; + + Cell get(CellLocation location) { + if (location.row < cells.length) { + if (location.col < cells[location.row].length) { + return cells[location.row][location.col]; + } + } + + return Cell.none; + } + + void set(CellLocation location, Cell cell) { + cells[location.row][location.col] = cell; + } + + bool isMoveAllowed({ + required List<int> source, + required List<int> target, + }) { + // printlog('(test) Pick from ' + source.toString() + ' and drop on ' + target.toString()); + final int sourceCol = source[0]; + final int sourceRow = source[1]; + final int targetCol = target[0]; + final int targetRow = target[1]; + + // ensure source and target are inside range + if (sourceRow < 0 || + sourceRow > (boardSize - 1) || + sourceCol < 0 || + sourceCol > (boardSize - 1)) { + // printlog('move forbidden: source is out of board'); + return false; + } + if (targetRow < 0 || + targetRow > (boardSize - 1) || + targetCol < 0 || + targetCol > (boardSize - 1)) { + // printlog('move forbidden: target is out of board'); + return false; + } + + // ensure source exists and has a peg + if (cells[sourceRow][sourceCol].hasPeg == false) { + // printlog('move forbidden: source peg does not exist'); + return false; + } + + // ensure target exists and is empty + if (cells[targetRow][targetCol].hasPeg == true) { + // printlog('move forbidden: target does not exist or already with a peg'); + return false; + } + + // ensure source and target are in the same line/column + if ((targetCol != sourceCol) && (targetRow != sourceRow)) { + // printlog('move forbidden: source and target are not in the same line or column'); + return false; + } + + // ensure source and target are separated by exactly one tile + if (((targetCol == sourceCol) && ((targetRow - sourceRow).abs() != 2)) || + ((targetRow == sourceRow) && ((targetCol - sourceCol).abs() != 2))) { + // printlog('move forbidden: source and target must be separated by exactly one tile'); + return false; + } + + // ensure middle tile exists and has a peg + final int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round(); + final int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round(); + if (cells[middleRow][middleCol].hasPeg == false) { + // printlog('move forbidden: tile between source and target does not contain a peg'); + return false; + } + + // ok, move is allowed + return true; + } + + void printGrid() { + String textBoard = ' '; + String textHole = '·'; + String textPeg = 'o'; + + printlog(''); + printlog('-------'); + for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { + String row = ''; + for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + String textCell = textBoard; + Cell tile = cells[rowIndex][colIndex]; + textCell = tile.hasPeg ? textPeg : textHole; + row += textCell; + } + printlog(row); + } + printlog('-------'); + printlog(''); + } + + void dump() { + printlog(''); + printlog('$Board:'); + printGrid(); + printlog(''); + } + + @override + String toString() { + return '$Board(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'cells': cells, + }; + } +} diff --git a/lib/models/cell.dart b/lib/models/cell.dart new file mode 100644 index 0000000..127c300 --- /dev/null +++ b/lib/models/cell.dart @@ -0,0 +1,41 @@ +import 'package:solitaire/models/cell_location.dart'; +import 'package:solitaire/utils/tools.dart'; + +class Cell { + Cell({ + required this.location, + required this.hasHole, + required this.hasPeg, + }); + + final CellLocation location; + bool hasHole; + bool hasPeg; + + static Cell none = Cell( + location: CellLocation.go(0, 0), + hasHole: false, + hasPeg: false, + ); + + void dump() { + printlog('$Cell:'); + printlog(' location: $location'); + printlog(' hasHole: $hasHole'); + printlog(' hasPeg: $hasPeg'); + printlog(''); + } + + @override + String toString() { + return '$Cell(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'location': location, + 'hasHole': hasHole, + 'hasPeg': hasPeg, + }; + } +} diff --git a/lib/models/cell_location.dart b/lib/models/cell_location.dart new file mode 100644 index 0000000..f9b9022 --- /dev/null +++ b/lib/models/cell_location.dart @@ -0,0 +1,34 @@ +import 'package:solitaire/utils/tools.dart'; + +class CellLocation { + final int col; + final int row; + + CellLocation({ + required this.col, + required this.row, + }); + + factory CellLocation.go(int row, int col) { + return CellLocation(col: col, row: row); + } + + void dump() { + printlog('$CellLocation:'); + printlog(' col: $col'); + printlog(' row: $row'); + printlog(''); + } + + @override + String toString() { + return '$CellLocation(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'col': col, + 'row': row, + }; + } +} diff --git a/lib/models/game.dart b/lib/models/game.dart new file mode 100644 index 0000000..009ff4f --- /dev/null +++ b/lib/models/game.dart @@ -0,0 +1,142 @@ +import 'package:solitaire/models/board.dart'; +import 'package:solitaire/models/cell.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/models/settings_global.dart'; +import 'package:solitaire/utils/tools.dart'; + +class Game { + Game({ + required this.gameSettings, + required this.globalSettings, + required this.board, + this.isRunning = false, + this.isFinished = false, + this.movesCount = 0, + this.remainingPegsCount = 0, + this.allowedMovesCount = 0, + }); + + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + bool isRunning = false; + bool isFinished = false; + + Board board; + + int movesCount = 0; + int remainingPegsCount = 0; + int allowedMovesCount = 0; + + factory Game.createNull() { + return Game( + gameSettings: GameSettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), + board: Board.createEmpty(), + ); + } + + factory Game.createNew({ + GameSettings? gameSettings, + GlobalSettings? globalSettings, + }) { + GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); + GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + return Game( + gameSettings: newGameSettings, + globalSettings: newGlobalSettings, + board: Board.createNew( + gameSettings: newGameSettings, + ), + isRunning: true, + ); + } + + int get boardSize => board.boardSize; + + bool get gameWon => (isFinished && (remainingPegsCount == 1)); + + List<Cell> listRemainingPegs() { + final List<Cell> pegs = []; + + final BoardCells cells = board.cells; + for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { + for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + Cell tile = cells[rowIndex][colIndex]; + if (tile.hasPeg == true) { + pegs.add(tile); + } + } + } + + return pegs; + } + + int countRemainingPegs() { + return listRemainingPegs().length; + } + + int countAllowedMoves() { + int allowedMovesCount = 0; + final List<Cell> pegs = listRemainingPegs(); + for (Cell tile in pegs) { + final int row = tile.location.row; + final int col = tile.location.col; + final List<int> source = [col, row]; + final List<List<int>> targets = [ + [col - 2, row], + [col + 2, row], + [col, row - 2], + [col, row + 2], + ]; + for (List<int> target in targets) { + if (board.isMoveAllowed( + source: source, + target: target, + )) { + allowedMovesCount++; + } + } + } + + return allowedMovesCount; + } + + void dump() { + printlog(''); + printlog('## Current game dump:'); + printlog(''); + gameSettings.dump(); + globalSettings.dump(); + printlog(''); + printlog(''); + printlog('$Game: '); + printlog(' isRunning: $isRunning'); + printlog(' isFinished: $isFinished'); + printlog(' movesCount: $movesCount'); + printlog(' remainingPegsCount: $remainingPegsCount'); + printlog(' allowedMovesCount: $allowedMovesCount'); + printlog(''); + board.dump(); + printlog(''); + } + + @override + String toString() { + return '$Game(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'gameSettings': gameSettings.toJson(), + 'globalSettings': globalSettings.toJson(), + 'board': board.toJson(), + 'isRunning': isRunning, + 'isFinished': isFinished, + 'movesCount': movesCount, + 'remainingPegsCount': remainingPegsCount, + 'allowedMovesCount': allowedMovesCount, + }; + } +} diff --git a/lib/models/settings_game.dart b/lib/models/settings_game.dart new file mode 100644 index 0000000..cb00bc9 --- /dev/null +++ b/lib/models/settings_game.dart @@ -0,0 +1,41 @@ +import 'package:solitaire/config/default_game_settings.dart'; +import 'package:solitaire/utils/tools.dart'; + +class GameSettings { + final String layout; + + GameSettings({ + required this.layout, + }); + + static String getLayoutValueFromUnsafe(String layout) { + if (DefaultGameSettings.allowedLayoutValues.contains(layout)) { + return layout; + } + + return DefaultGameSettings.defaultLayoutValue; + } + + factory GameSettings.createDefault() { + return GameSettings( + layout: DefaultGameSettings.defaultLayoutValue, + ); + } + + void dump() { + printlog('$GameSettings: '); + printlog(' ${DefaultGameSettings.parameterCodeLayout}: $layout'); + printlog(''); + } + + @override + String toString() { + return '$GameSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeLayout: layout, + }; + } +} diff --git a/lib/models/settings_global.dart b/lib/models/settings_global.dart new file mode 100644 index 0000000..dd4b7c8 --- /dev/null +++ b/lib/models/settings_global.dart @@ -0,0 +1,41 @@ +import 'package:solitaire/config/default_global_settings.dart'; +import 'package:solitaire/utils/tools.dart'; + +class GlobalSettings { + String skin; + + GlobalSettings({ + required this.skin, + }); + + static String getSkinValueFromUnsafe(String skin) { + if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) { + return skin; + } + + return DefaultGlobalSettings.defaultSkinValue; + } + + factory GlobalSettings.createDefault() { + return GlobalSettings( + skin: DefaultGlobalSettings.defaultSkinValue, + ); + } + + void dump() { + printlog('$GlobalSettings: '); + printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin'); + printlog(''); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: skin, + }; + } +} diff --git a/lib/models/types.dart b/lib/models/types.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index 9c18e79..0000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,239 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:solitaire/entities/tile.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -typedef Board = List<List<Tile?>>; - -class Data extends ChangeNotifier { - // Configuration available values - final List<String> _availableParameters = ['layout', 'skin']; - - final List<String> _availableLayoutValues = ['french', 'german', 'english', 'diamond']; - final List<String> _availableSkinValues = ['default']; - - List<String> get availableParameters => _availableParameters; - List<String> get availableLayoutValues => _availableLayoutValues; - List<String> get availableSkinValues => _availableSkinValues; - - // Application default configuration - String _parameterLayout = ''; - String _parameterSkin = ''; - final String _parameterLayoutDefault = 'english'; - final String _parameterSkinDefault = 'default'; - - // Application current configuration - String get parameterLayout => _parameterLayout; - String get parameterSkin => _parameterSkin; - - // Game data - bool _assetsPreloaded = false; - bool _gameIsRunning = false; - bool _gameIsFinished = false; - Board _board = []; - int _boardSize = 0; - double _tileSize = 0; - int _movesCount = 0; - int _remainingPegsCount = 0; - int _allowedMovesCount = 0; - String _currentState = ''; - - void updateParameterLayout(String parameterLayout) { - _parameterLayout = parameterLayout; - notifyListeners(); - } - - void updateParameterSkin(String parameterSkin) { - _parameterSkin = parameterSkin; - notifyListeners(); - } - - String getParameterValue(String parameterCode) { - switch (parameterCode) { - case 'layout': - return _parameterLayout; - case 'skin': - return _parameterSkin; - } - return ''; - } - - List getParameterAvailableValues(String parameterCode) { - switch (parameterCode) { - case 'layout': - return _availableLayoutValues; - case 'skin': - return _availableSkinValues; - } - return []; - } - - void setParameterValue(String parameterCode, String parameterValue) async { - switch (parameterCode) { - case 'layout': - updateParameterLayout(parameterValue); - break; - case 'skin': - updateParameterSkin(parameterValue); - break; - } - final prefs = await SharedPreferences.getInstance(); - prefs.setString(parameterCode, parameterValue); - } - - void initParametersValues() async { - final prefs = await SharedPreferences.getInstance(); - setParameterValue('layout', prefs.getString('layout') ?? _parameterLayoutDefault); - setParameterValue('skin', prefs.getString('skin') ?? _parameterSkinDefault); - } - - String get currentState => _currentState; - - String computeCurrentGameState() { - String boardValues = ''; - - String textBoard = ' '; - String textHole = '·'; - String textPeg = 'o'; - for (int rowIndex = 0; rowIndex < _board.length; rowIndex++) { - for (int colIndex = 0; colIndex < _board[rowIndex].length; colIndex++) { - String cellValue = textBoard; - if (_board[rowIndex][colIndex] != null) { - cellValue = (_board[rowIndex][colIndex]?.hasPeg ?? false) ? textPeg : textHole; - } - boardValues += cellValue; - } - } - - var currentState = { - 'layout': _parameterLayout, - 'skin': _parameterSkin, - 'movesCount': _movesCount.toString(), - 'boardValues': boardValues, - }; - - return json.encode(currentState); - } - - void saveCurrentGameState() async { - if (_gameIsRunning) { - _currentState = computeCurrentGameState(); - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('savedState', _currentState); - } else { - resetCurrentSavedState(); - } - } - - void resetCurrentSavedState() async { - _currentState = ''; - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('savedState', _currentState); - notifyListeners(); - } - - void loadCurrentSavedState() async { - final prefs = await SharedPreferences.getInstance(); - _currentState = prefs.getString('savedState') ?? ''; - } - - bool hasCurrentSavedState() { - return (_currentState != ''); - } - - Map<String, dynamic> getCurrentSavedState() { - if (_currentState != '') { - final Map<String, dynamic> savedState = json.decode(_currentState); - if (savedState.isNotEmpty) { - return savedState; - } - } - return {}; - } - - bool get assetsPreloaded => _assetsPreloaded; - void updateAssetsPreloaded(bool assetsPreloaded) { - _assetsPreloaded = assetsPreloaded; - } - - double get tileSize => _tileSize; - void updateTileSize(double tileSize) { - _tileSize = tileSize; - } - - int get boardSize => _boardSize; - void updateBoardSize(int boardSize) { - _boardSize = boardSize; - } - - Board get board => _board; - void updateBoard(Board board) { - _board = board; - updateBoardSize(board.length); - updateRemainingPegsCount(GameUtils.countRemainingPegs(this)); - updateAllowedMovesCount(GameUtils.countAllowedMoves(this)); - notifyListeners(); - } - - updatePegValue(int row, int col, bool hasPeg) { - if (_board[row][col] != null) { - _board[row][col]?.hasPeg = hasPeg; - - saveCurrentGameState(); - notifyListeners(); - } - } - - int get movesCount => _movesCount; - void updateMovesCount(int movesCount) { - _movesCount = movesCount; - notifyListeners(); - } - - void incrementMovesCount() { - updateMovesCount(movesCount + 1); - } - - int get remainingPegsCount => _remainingPegsCount; - void updateRemainingPegsCount(int remainingPegsCount) { - _remainingPegsCount = remainingPegsCount; - notifyListeners(); - } - - int get allowedMovesCount => _allowedMovesCount; - void updateAllowedMovesCount(int allowedMovesCount) { - _allowedMovesCount = allowedMovesCount; - if (allowedMovesCount == 0) { - updateGameIsFinished(true); - } - notifyListeners(); - } - - bool get gameIsRunning => _gameIsRunning; - bool get gameIsFinished => _gameIsFinished; - void updateGameIsRunning(bool gameIsRunning) { - _gameIsRunning = gameIsRunning; - notifyListeners(); - } - - void updateGameIsFinished(bool gameIsFinished) { - _gameIsFinished = gameIsFinished; - notifyListeners(); - } - - bool gameWon() { - return gameIsFinished && (remainingPegsCount == 1); - } - - void resetGame() { - _gameIsRunning = false; - _gameIsFinished = false; - _movesCount = 0; - _remainingPegsCount = 0; - notifyListeners(); - } -} diff --git a/lib/ui/layout/game.dart b/lib/ui/layout/game.dart deleted file mode 100644 index aa9d47c..0000000 --- a/lib/ui/layout/game.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/layout/tileset.dart'; -import 'package:solitaire/ui/widgets/game/indicator_top.dart'; -import 'package:solitaire/ui/widgets/game/message_game_end.dart'; - -class Game extends StatelessWidget { - const Game({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final bool gameIsFinished = myProvider.gameIsFinished; - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 8), - TopIndicator(myProvider: myProvider), - const SizedBox(height: 2), - Expanded( - child: Tileset(myProvider: myProvider), - ), - const SizedBox(height: 2), - Container( - child: gameIsFinished - ? EndGameMessage(myProvider: myProvider) - : const SizedBox(height: 2), - ), - ], - ); - } -} diff --git a/lib/ui/layout/parameters.dart b/lib/ui/layout/parameters.dart deleted file mode 100644 index 68d9ac8..0000000 --- a/lib/ui/layout/parameters.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/widgets/home/button_game_resume.dart'; -import 'package:solitaire/ui/widgets/home/button_game_start_new.dart'; - -class Parameters extends StatelessWidget { - const Parameters({super.key, required this.myProvider}); - - final Data myProvider; - - static const double separatorHeight = 2.0; - static const double blockMargin = 0.0; - static const double blockPadding = 0.0; - static const Color buttonBackgroundColor = Colors.white; - static const Color buttonBorderColorActive = Colors.blue; - static const Color buttonBorderColorInactive = Colors.white; - static const double buttonBorderWidth = 6.0; - static const double buttonBorderRadius = 8.0; - static const double buttonPadding = 0.0; - static const double buttonMargin = 0.0; - - @override - Widget build(BuildContext context) { - List<Widget> lines = []; - - List parameters = myProvider.availableParameters; - for (int index = 0; index < parameters.length; index++) { - lines.add(buildParameterSelector(myProvider, parameters[index])); - lines.add(const SizedBox(height: separatorHeight)); - } - - myProvider.loadCurrentSavedState(); - Widget buttonsBlock = myProvider.hasCurrentSavedState() - ? ResumeGameButton(myProvider: myProvider) - : StartNewGameButton(myProvider: myProvider); - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: separatorHeight), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: lines, - ), - ), - const SizedBox(height: separatorHeight), - Container( - child: buttonsBlock, - ), - ], - ); - } - - static Image buildImageWidget(String imageAssetCode) { - return Image( - image: AssetImage('assets/icons/$imageAssetCode.png'), - fit: BoxFit.fill, - ); - } - - static Container buildImageContainerWidget(String imageAssetCode) { - return Container( - child: buildImageWidget(imageAssetCode), - ); - } - - static Column buildDecorationImageWidget() { - return Column( - children: [ - TextButton( - child: buildImageContainerWidget('placeholder'), - onPressed: () {}, - ), - ], - ); - } - - Widget buildParameterSelector(Data myProvider, String parameterCode) { - List availableValues = myProvider.getParameterAvailableValues(parameterCode); - - if (availableValues.length == 1) { - return const SizedBox(height: 0.0); - } - - return Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - for (int index = 0; index < availableValues.length; index++) - Column( - children: [ - buildParameterButton(myProvider, parameterCode, availableValues[index]) - ], - ), - ], - ), - ], - ); - } - - Widget buildParameterButton(Data myProvider, String parameterCode, String parameterValue) { - String currentValue = myProvider.getParameterValue(parameterCode).toString(); - - bool isActive = (parameterValue == currentValue); - String imageAsset = '${parameterCode}_$parameterValue'; - - return TextButton( - child: Container( - margin: const EdgeInsets.all(buttonMargin), - padding: const EdgeInsets.all(buttonPadding), - decoration: BoxDecoration( - color: buttonBackgroundColor, - borderRadius: BorderRadius.circular(buttonBorderRadius), - border: Border.all( - color: isActive ? buttonBorderColorActive : buttonBorderColorInactive, - width: buttonBorderWidth, - ), - ), - child: buildImageWidget(imageAsset), - ), - onPressed: () => myProvider.setParameterValue(parameterCode, parameterValue), - ); - } -} diff --git a/lib/ui/layout/tileset.dart b/lib/ui/layout/tileset.dart deleted file mode 100644 index 4fe85a7..0000000 --- a/lib/ui/layout/tileset.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; - -class Tileset extends StatelessWidget { - const Tileset({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final Board board = myProvider.board; - - Widget boardTileWithoutHole = Image( - image: AssetImage('assets/skins/${myProvider.parameterSkin}_board.png'), - width: myProvider.tileSize, - height: myProvider.tileSize, - fit: BoxFit.fill, - ); - - return Column( - children: [ - Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - for (int row = 0; row < board.length; row++) - TableRow( - children: [ - for (int col = 0; col < board[row].length; col++) - TableCell( - child: board[row][col] != null - ? (board[row][col]?.render(myProvider) ?? Container()) - : boardTileWithoutHole, - ), - ], - ), - ], - ), - ], - ); - } -} diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart new file mode 100644 index 0000000..4ed92d7 --- /dev/null +++ b/lib/ui/painters/parameter_painter.dart @@ -0,0 +1,190 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:solitaire/config/default_game_settings.dart'; +import 'package:solitaire/data/game_data.dart'; +import 'package:solitaire/models/board.dart'; +import 'package:solitaire/models/cell.dart'; +import 'package:solitaire/models/cell_location.dart'; +import 'package:solitaire/models/settings_game.dart'; +import 'package:solitaire/models/settings_global.dart'; +import 'package:solitaire/utils/tools.dart'; + +class ParameterPainter extends CustomPainter { + const ParameterPainter({ + required this.code, + required this.value, + required this.isSelected, + required this.gameSettings, + required this.globalSettings, + }); + + final String code; + final String value; + final bool isSelected; + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + @override + void paint(Canvas canvas, Size size) { + // force square + final double canvasSize = min(size.width, size.height); + + const Color borderColorEnabled = Colors.blue; + const Color borderColorDisabled = Colors.white; + + // "enabled/disabled" border + final paint = Paint(); + paint.style = PaintingStyle.stroke; + paint.color = isSelected ? borderColorEnabled : borderColorDisabled; + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 10; + canvas.drawRect( + Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint); + + // content + switch (code) { + case DefaultGameSettings.parameterCodeLayout: + paintLayoutParameterItem(value, canvas, canvasSize); + break; + default: + printlog('Unknown parameter: $code/$value'); + paintUnknownParameterItem(value, canvas, canvasSize); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } + + // "unknown" parameter -> simple block with text + void paintUnknownParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + paint.color = Colors.grey; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + final textSpan = TextSpan( + text: '?\n$value', + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } + + void paintLayoutParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + { + const Color backgroundColor = Colors.white; + + Color gridBackgroundColor = Colors.grey; + + switch (value) { + case DefaultGameSettings.layoutValueFrench: + gridBackgroundColor = Colors.green; + break; + case DefaultGameSettings.layoutValueGerman: + gridBackgroundColor = Colors.orange; + break; + case DefaultGameSettings.layoutValueEnglish: + gridBackgroundColor = Colors.red; + break; + case DefaultGameSettings.layoutValueDiamond: + gridBackgroundColor = Colors.purple; + break; + default: + printlog('Wrong value for size parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Mini grid + final borderColor = Colors.grey.shade800; + + // Get board data from named templates + final List<String>? template = GameData.templates[value]; + final BoardCells grid = []; + + int row = 0; + template?.forEach((String line) { + final List<Cell> gridLine = []; + int col = 0; + line.split("").forEach((String tileCode) { + gridLine.add(tileCode == ' ' + ? Cell.none + : Cell( + location: CellLocation.go(row, col), + hasHole: true, + hasPeg: (tileCode == 'o'), + )); + col++; + }); + row++; + grid.add(gridLine); + }); + + final int gridWidth = grid.length; + final int gridHeight = grid.first.length; + paint.strokeWidth = 2; + + final double cellSize = (size * .9) / max(gridWidth, gridHeight); + final double originX = (size - gridWidth * cellSize) / 2; + final double originY = (size - gridHeight * cellSize) / 2; + + for (int row = 0; row < gridHeight; row++) { + for (int col = 0; col < gridWidth; col++) { + final Offset topLeft = Offset(originX + col * cellSize, originY + row * cellSize); + final Offset bottomRight = topLeft + Offset(cellSize, cellSize); + + paint.color = Colors.white; + paint.style = PaintingStyle.fill; + + if (grid[row][col].hasPeg) { + paint.color = gridBackgroundColor; + } + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + if (grid[row][col].hasHole) { + paint.color = borderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + } + } + } + } +} diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart deleted file mode 100644 index 9171fc2..0000000 --- a/lib/ui/screens/game_page.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/layout/game.dart'; -import 'package:solitaire/ui/layout/parameters.dart'; - -class GamePage extends StatefulWidget { - const GamePage({super.key}); - - @override - GamePageState createState() => GamePageState(); -} - -class GamePageState extends State<GamePage> { - @override - void initState() { - super.initState(); - - final Data myProvider = Provider.of<Data>(context, listen: false); - myProvider.initParametersValues(); - myProvider.loadCurrentSavedState(); - } - - List<String> getImagesAssets(Data myProvider) { - final List<String> assets = []; - - final List<String> gameImages = [ - 'button_back', - 'button_delete_saved_game', - 'button_resume_game', - 'button_start', - 'game_fail', - 'game_win', - 'placeholder', - ]; - for (String layout in myProvider.availableLayoutValues) { - gameImages.add('layout_$layout'); - } - for (String skin in myProvider.availableSkinValues) { - gameImages.add('skin_$skin'); - } - - for (String image in gameImages) { - assets.add('${'assets/icons/$image'}.png'); - } - - const List<String> skinImages = [ - 'board', - 'hole', - 'peg', - ]; - - for (String skin in myProvider.availableSkinValues) { - for (String image in skinImages) { - assets.add('${'${'assets/skins/$skin'}_$image'}.png'); - } - } - - return assets; - } - - @override - Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - - if (!myProvider.assetsPreloaded) { - final List<String> assets = getImagesAssets(myProvider); - for (String asset in assets) { - precacheImage(AssetImage(asset), context); - } - myProvider.updateAssetsPreloaded(true); - } - - myProvider.updateTileSize((MediaQuery.of(context).size.width - 40) / myProvider.boardSize); - - return SafeArea( - child: Center( - child: myProvider.gameIsRunning - ? Game(myProvider: myProvider) - : Parameters(myProvider: myProvider), - ), - ); - } -} diff --git a/lib/ui/screens/about_page.dart b/lib/ui/screens/page_about.dart similarity index 89% rename from lib/ui/screens/about_page.dart rename to lib/ui/screens/page_about.dart index f22e2ad..f63e153 100644 --- a/lib/ui/screens/about_page.dart +++ b/lib/ui/screens/page_about.dart @@ -2,10 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:solitaire/ui/widgets/header_app.dart'; +import 'package:solitaire/ui/widgets/helpers/app_header.dart'; -class AboutPage extends StatelessWidget { - const AboutPage({super.key}); +class PageAbout extends StatelessWidget { + const PageAbout({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart new file mode 100644 index 0000000..287a383 --- /dev/null +++ b/lib/ui/screens/page_game.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/ui/widgets/game/game_widget.dart'; +import 'package:solitaire/ui/widgets/parameters.dart'; + +class PageGame extends StatelessWidget { + const PageGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return gameState.currentGame.isRunning ? const GameWidget() : const Parameters(); + }, + ); + } +} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/page_settings.dart similarity index 80% rename from lib/ui/screens/settings_page.dart rename to lib/ui/screens/page_settings.dart index 63cb3f2..ae65ab9 100644 --- a/lib/ui/screens/settings_page.dart +++ b/lib/ui/screens/page_settings.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:solitaire/ui/widgets/header_app.dart'; +import 'package:solitaire/ui/widgets/helpers/app_header.dart'; import 'package:solitaire/ui/widgets/settings/settings_form.dart'; -class SettingsPage extends StatelessWidget { - const SettingsPage({super.key}); +class PageSettings extends StatelessWidget { + const PageSettings({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index 36cd485..c57f5c2 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,32 +1,33 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; import 'package:solitaire/config/menu.dart'; -import 'package:solitaire/cubit/bottom_nav_cubit.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/widgets/app_bar.dart'; -import 'package:solitaire/ui/widgets/bottom_nav_bar.dart'; +import 'package:solitaire/cubit/nav_cubit.dart'; +import 'package:solitaire/ui/widgets/global_app_bar.dart'; -class SkeletonScreen extends StatefulWidget { +class SkeletonScreen extends StatelessWidget { const SkeletonScreen({super.key}); - @override - State<SkeletonScreen> createState() => _SkeletonScreenState(); -} - -class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - return Scaffold( + appBar: const GlobalAppBar(), extendBodyBehindAppBar: false, - appBar: StandardAppBar(myProvider: myProvider), - bottomNavigationBar: const BottomNavBar(), - body: BlocBuilder<BottomNavCubit, int>(builder: (BuildContext context, int state) { - return Menu.getPageWidget(state); - }), + body: Material( + color: Theme.of(context).colorScheme.background, + child: BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + return Padding( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), + child: Menu.getPageWidget(pageIndex), + ); + }, + ), + ), backgroundColor: Theme.of(context).colorScheme.background, ); } diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart deleted file mode 100644 index 1dc274e..0000000 --- a/lib/ui/widgets/app_bar.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:overlay_support/overlay_support.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/widgets/header_app.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class StandardAppBar extends StatelessWidget implements PreferredSizeWidget { - const StandardAppBar({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final List<Widget> menuActions = []; - - if (myProvider.gameIsRunning) { - menuActions.add(TextButton( - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () => toast(tr('long_press_to_quit')), - onLongPress: () => GameUtils.quitGame(myProvider), - )); - } - - return AppBar( - title: const AppHeader(text: 'app_name'), - actions: menuActions, - ); - } - - @override - Size get preferredSize => const Size.fromHeight(50); -} diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart deleted file mode 100644 index 7e3d471..0000000 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:solitaire/config/menu.dart'; -import 'package:solitaire/cubit/bottom_nav_cubit.dart'; - -class BottomNavBar extends StatelessWidget { - const BottomNavBar({super.key}); - - @override - Widget build(BuildContext context) { - return Card( - margin: const EdgeInsets.all(0), - elevation: 4, - shadowColor: Theme.of(context).colorScheme.shadow, - color: Theme.of(context).colorScheme.surfaceVariant, - shape: const ContinuousRectangleBorder(), - child: BlocBuilder<BottomNavCubit, int>( - builder: (BuildContext context, int state) { - return BottomNavigationBar( - currentIndex: state, - onTap: (int index) { - context.read<BottomNavCubit>().updateIndex(index); - }, - type: BottomNavigationBarType.fixed, - elevation: 0, - backgroundColor: Colors.transparent, - selectedItemColor: Theme.of(context).colorScheme.primary, - unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color, - items: Menu.getMenuItems(), - ); - }, - ), - ); - } -} diff --git a/lib/ui/widgets/button_game_start_new.dart b/lib/ui/widgets/button_game_start_new.dart new file mode 100644 index 0000000..cd4b8f0 --- /dev/null +++ b/lib/ui/widgets/button_game_start_new.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/cubit/settings_game_cubit.dart'; +import 'package:solitaire/cubit/settings_global_cubit.dart'; + +class StartNewGameButton extends StatelessWidget { + const StartNewGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + return TextButton( + child: const Image( + image: AssetImage('assets/icons/button_start.png'), + fit: BoxFit.fill, + ), + onPressed: () => gameCubit.startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/game_widget.dart b/lib/ui/widgets/game/game_widget.dart new file mode 100644 index 0000000..e2c7caf --- /dev/null +++ b/lib/ui/widgets/game/game_widget.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/game.dart'; +import 'package:solitaire/ui/widgets/game/indicator_top.dart'; +import 'package:solitaire/ui/widgets/game/message_game_end.dart'; +import 'package:solitaire/ui/widgets/game/tileset.dart'; + +class GameWidget extends StatelessWidget { + const GameWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final bool gameIsFinished = currentGame.isFinished; + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 8), + const TopIndicator(), + const SizedBox(height: 2), + const Expanded( + child: Tileset(), + ), + const SizedBox(height: 2), + gameIsFinished ? const EndGameMessage() : const SizedBox(height: 2), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/indicator_top.dart b/lib/ui/widgets/game/indicator_top.dart index 7ce0c3e..bc66f79 100644 --- a/lib/ui/widgets/game/indicator_top.dart +++ b/lib/ui/widgets/game/indicator_top.dart @@ -1,47 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:solitaire/provider/data.dart'; +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/game.dart'; class TopIndicator extends StatelessWidget { - const TopIndicator({super.key, required this.myProvider}); - - final Data myProvider; + const TopIndicator({super.key}); @override Widget build(BuildContext context) { - final int allowedMovesCount = myProvider.allowedMovesCount; + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; - return Table( - children: [ - TableRow( + return Table( children: [ - Column( + TableRow( children: [ - Text( - '♟️ ${myProvider.remainingPegsCount}', - style: const TextStyle( - fontSize: 40, - fontWeight: FontWeight.w600, - color: Colors.black, - ), + Column( + children: [ + Text( + '♟️ ${currentGame.remainingPegsCount}', + style: const TextStyle( + fontSize: 40, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ], ), - ], - ), - Column( - children: [ - Text( - allowedMovesCount.toString(), - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.green, - ), + Column( + children: [ + Text( + currentGame.allowedMovesCount.toString(), + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.green, + ), + ), + ], ), ], ), ], - ), - ], + ); + }, ); } } diff --git a/lib/ui/widgets/game/message_game_end.dart b/lib/ui/widgets/game/message_game_end.dart index cd6ca3a..b1b4faa 100644 --- a/lib/ui/widgets/game/message_game_end.dart +++ b/lib/ui/widgets/game/message_game_end.dart @@ -1,42 +1,56 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/widgets/home/button_game_restart.dart'; +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/game.dart'; class EndGameMessage extends StatelessWidget { - const EndGameMessage({super.key, required this.myProvider}); - - final Data myProvider; + const EndGameMessage({super.key}); @override Widget build(BuildContext context) { - String decorationImageAssetName = ''; - if (myProvider.gameWon()) { - decorationImageAssetName = 'assets/icons/game_win.png'; - } else { - decorationImageAssetName = 'assets/icons/placeholder.png'; - } + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; - final Image decorationImage = Image( - image: AssetImage(decorationImageAssetName), - fit: BoxFit.fill, - ); + String decorationImageAssetName = ''; + if (currentGame.gameWon) { + decorationImageAssetName = 'assets/icons/game_win.png'; + } else { + decorationImageAssetName = 'assets/icons/placeholder.png'; + } + + final Image decorationImage = Image( + image: AssetImage(decorationImageAssetName), + fit: BoxFit.fill, + ); - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), children: [ - Column(children: [decorationImage]), - Column(children: [RestartGameButton(myProvider: myProvider)]), - Column(children: [decorationImage]), + TableRow( + children: [ + Column(children: [decorationImage]), + TextButton( + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.quitGame(); + }, + ), + Column(children: [decorationImage]), + ], + ), ], ), - ], - ), + ); + }, ); } } diff --git a/lib/ui/widgets/game/tile_widget.dart b/lib/ui/widgets/game/tile_widget.dart new file mode 100644 index 0000000..7d71df5 --- /dev/null +++ b/lib/ui/widgets/game/tile_widget.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/cell.dart'; +import 'package:solitaire/models/game.dart'; + +class TileWidget extends StatelessWidget { + const TileWidget({ + super.key, + required this.tile, + required this.tileSize, + }); + + final Cell tile; + final double tileSize; + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + if (tile.hasHole) { + return render( + currentGame: currentGame, + gameCubit: gameCubit, + ); + } else { + return Image( + image: AssetImage('assets/skins/${currentGame.globalSettings.skin}_board.png'), + width: tileSize, + height: tileSize, + fit: BoxFit.fill, + ); + } + }, + ); + } + + Widget render({ + required Game currentGame, + required GameCubit gameCubit, + }) { + List<Widget> stack = [ + hole( + currentGame: currentGame, + gameCubit: gameCubit, + ), + ]; + + if (tile.hasPeg) { + stack.add(draggable( + currentGame: currentGame, + )); + } + + return Stack( + alignment: Alignment.center, + children: stack, + ); + } + + Widget hole({ + required Game currentGame, + required GameCubit gameCubit, + }) { + String image = 'assets/skins/${currentGame.globalSettings.skin}_hole.png'; + + return DragTarget<List<int>>( + builder: ( + BuildContext context, + List<dynamic> accepted, + List<dynamic> rejected, + ) { + return Image( + image: AssetImage(image), + width: tileSize, + height: tileSize, + fit: BoxFit.fill, + ); + }, + onAcceptWithDetails: (DragTargetDetails<List<int>> source) { + List<int> target = [tile.location.col, tile.location.row]; + // printlog('(drag) Pick from ' + source.toString() + ' and drop on ' + target.toString()); + if (currentGame.board.isMoveAllowed( + source: source.data, + target: target, + )) { + gameCubit.move( + currentGame: currentGame, + source: source.data, + target: target, + ); + } + }, + ); + } + + Widget draggable({ + required Game currentGame, + }) { + return Draggable<List<int>>( + data: [tile.location.col, tile.location.row], + + // Widget when draggable is being dragged + feedback: peg( + currentGame: currentGame, + ), + + // Widget to display on original place when being dragged + childWhenDragging: Container(), + + // Widget when draggable is stationary + child: peg( + currentGame: currentGame, + ), + ); + } + + Widget peg({ + required Game currentGame, + }) { + String image = 'assets/skins/${currentGame.globalSettings.skin}_peg.png'; + + return Image( + image: AssetImage(image), + width: tileSize, + height: tileSize, + fit: BoxFit.fill, + ); + } +} diff --git a/lib/ui/widgets/game/tileset.dart b/lib/ui/widgets/game/tileset.dart new file mode 100644 index 0000000..5462a79 --- /dev/null +++ b/lib/ui/widgets/game/tileset.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/models/board.dart'; +import 'package:solitaire/ui/widgets/game/tile_widget.dart'; + +class Tileset extends StatelessWidget { + const Tileset({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Board board = gameState.currentGame.board; + final BoardCells cells = board.cells; + + final boardSize = gameState.currentGame.boardSize; + final double tileSize = (MediaQuery.of(context).size.width - 40) / boardSize; + + return Column( + children: [ + Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + for (int row = 0; row < cells.length; row++) + TableRow( + children: [ + for (int col = 0; col < cells[row].length; col++) + TableCell( + child: TileWidget( + tile: cells[row][col], + tileSize: tileSize, + ), + ), + ], + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart new file mode 100644 index 0000000..8212103 --- /dev/null +++ b/lib/ui/widgets/global_app_bar.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/config/menu.dart'; +import 'package:solitaire/cubit/game_cubit.dart'; +import 'package:solitaire/cubit/nav_cubit.dart'; +import 'package:solitaire/models/game.dart'; +import 'package:solitaire/ui/widgets/helpers/app_title.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + final Game currentGame = gameState.currentGame; + + final List<Widget> menuActions = []; + + if (currentGame.isRunning) { + menuActions.add(TextButton( + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () {}, + onLongPress: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.quitGame(); + }, + )); + } else { + if (pageIndex == Menu.indexGame) { + // go to Settings page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToSettingsPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemSettings.icon, + )); + + // go to About page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToAboutPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemAbout.icon, + )); + } else { + // back to Home page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToGamePage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemGame.icon, + )); + } + } + + return AppBar( + title: const AppTitle(text: 'app_name'), + actions: menuActions, + ); + }, + ); + }, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/helpers/app_header.dart similarity index 77% rename from lib/ui/widgets/header_app.dart rename to lib/ui/widgets/helpers/app_header.dart index bf54b77..b5c5be0 100644 --- a/lib/ui/widgets/header_app.dart +++ b/lib/ui/widgets/helpers/app_header.dart @@ -8,15 +8,16 @@ class AppHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( + return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( tr(text), textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + style: Theme.of(context).textTheme.headlineSmall!.apply(fontWeightDelta: 2), ), + const SizedBox(height: 8), ], ); } diff --git a/lib/ui/widgets/helpers/app_title.dart b/lib/ui/widgets/helpers/app_title.dart new file mode 100644 index 0000000..7cbbb20 --- /dev/null +++ b/lib/ui/widgets/helpers/app_title.dart @@ -0,0 +1,17 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class AppTitle extends StatelessWidget { + const AppTitle({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineLarge!.apply(fontWeightDelta: 2), + ); + } +} diff --git a/lib/ui/widgets/home/button_game_restart.dart b/lib/ui/widgets/home/button_game_restart.dart deleted file mode 100644 index 48dfc34..0000000 --- a/lib/ui/widgets/home/button_game_restart.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class RestartGameButton extends StatelessWidget { - const RestartGameButton({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - return TextButton( - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () => GameUtils.quitAndDeleteCurrentGame(myProvider), - ); - } -} diff --git a/lib/ui/widgets/home/button_game_resume.dart b/lib/ui/widgets/home/button_game_resume.dart deleted file mode 100644 index 185c92f..0000000 --- a/lib/ui/widgets/home/button_game_resume.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/ui/layout/parameters.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class ResumeGameButton extends Parameters { - const ResumeGameButton({super.key, required super.myProvider}); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(Parameters.blockMargin), - padding: const EdgeInsets.all(Parameters.blockPadding), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_delete_saved_game'), - onPressed: () => GameUtils.deleteSavedGame(myProvider), - ), - ], - ), - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_resume_game'), - onPressed: () => GameUtils.resumeSavedGame(myProvider), - ), - ], - ), - Parameters.buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/home/button_game_start_new.dart b/lib/ui/widgets/home/button_game_start_new.dart deleted file mode 100644 index 2de0c55..0000000 --- a/lib/ui/widgets/home/button_game_start_new.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/ui/layout/parameters.dart'; -import 'package:solitaire/utils/game_utils.dart'; - -class StartNewGameButton extends StatelessWidget { - const StartNewGameButton({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(Parameters.blockMargin), - padding: const EdgeInsets.all(Parameters.blockPadding), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Parameters.buildDecorationImageWidget(), - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_start'), - onPressed: () => GameUtils.startNewGame(myProvider), - ), - ], - ), - Parameters.buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/parameters.dart b/lib/ui/widgets/parameters.dart new file mode 100644 index 0000000..11b080d --- /dev/null +++ b/lib/ui/widgets/parameters.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:solitaire/config/default_game_settings.dart'; +import 'package:solitaire/config/default_global_settings.dart'; +import 'package:solitaire/cubit/settings_game_cubit.dart'; +import 'package:solitaire/cubit/settings_global_cubit.dart'; +import 'package:solitaire/ui/painters/parameter_painter.dart'; +import 'package:solitaire/ui/widgets/button_game_start_new.dart'; + +class Parameters extends StatelessWidget { + const Parameters({super.key}); + + final double separatorHeight = 8.0; + + @override + Widget build(BuildContext context) { + final List<Widget> lines = []; + + // Game settings + for (String code in DefaultGameSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: false, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + lines.add(SizedBox(height: separatorHeight)); + lines.add(const Expanded(child: StartNewGameButton())); + lines.add(SizedBox(height: separatorHeight)); + + // Global settings + for (String code in DefaultGlobalSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: true, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + return Column( + children: lines, + ); + } + + List<Widget> buildParametersLine({ + required String code, + required bool isGlobal, + }) { + final List<Widget> parameterButtons = []; + + final List<String> availableValues = isGlobal + ? DefaultGlobalSettings.getAvailableValues(code) + : DefaultGameSettings.getAvailableValues(code); + + if (availableValues.length <= 1) { + return []; + } + + for (String value in availableValues) { + final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameSettingsCubit gameSettingsCubit = + BlocProvider.of<GameSettingsCubit>(context); + final GlobalSettingsCubit globalSettingsCubit = + BlocProvider.of<GlobalSettingsCubit>(context); + + final String currentValue = isGlobal + ? globalSettingsCubit.getParameterValue(code) + : gameSettingsCubit.getParameterValue(code); + + final bool isActive = (value == currentValue); + + final double displayWidth = MediaQuery.of(context).size.width; + final double itemWidth = displayWidth / availableValues.length - 26; + + return TextButton( + child: CustomPaint( + size: Size(itemWidth, itemWidth), + willChange: false, + painter: ParameterPainter( + code: code, + value: value, + isSelected: isActive, + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + isComplex: true, + ), + onPressed: () => isGlobal + ? globalSettingsCubit.setParameterValue(code, value) + : gameSettingsCubit.setParameterValue(code, value), + ); + }, + ); + }, + ); + + parameterButtons.add(parameterButton); + } + + return parameterButtons; + } +} diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart deleted file mode 100644 index 349b05d..0000000 --- a/lib/utils/board_utils.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'dart:math'; - -import 'package:solitaire/entities/tile.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/utils/tools.dart'; - -class BoardUtils { - static printGrid(List cells) { - String textBoard = ' '; - String textHole = '·'; - String textPeg = 'o'; - - printlog(''); - printlog('-------'); - for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { - String row = ''; - for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { - String textCell = textBoard; - Tile? tile = cells[rowIndex][colIndex]; - if (tile != null) { - textCell = tile.hasPeg ? textPeg : textHole; - } - row += textCell; - } - printlog(row); - } - printlog('-------'); - printlog(''); - } - - static Board createBoardFromSavedState(Data myProvider, String savedBoard) { - Board board = []; - int boardSize = pow((savedBoard.length), 1 / 2).round(); - myProvider.updateBoardSize(boardSize); - - String textBoard = ' '; - String textPeg = 'o'; - - int index = 0; - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - List<Tile?> row = []; - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - String stringValue = savedBoard[index++]; - if (stringValue == textBoard) { - row.add(null); - } else { - row.add(Tile(rowIndex, colIndex, (stringValue == textPeg))); - } - } - board.add(row); - } - - return board; - } - - static createNewBoard(Data myProvider) { - Map<String, List<String>> templates = { - 'french': [ - ' ooo ', - ' ooooo ', - 'ooo·ooo', - 'ooooooo', - 'ooooooo', - ' ooooo ', - ' ooo ', - ], - 'german': [ - ' ooo ', - ' ooo ', - ' ooo ', - 'ooooooooo', - 'oooo·oooo', - 'ooooooooo', - ' ooo ', - ' ooo ', - ' ooo ', - ], - 'english': [ - ' ooo ', - ' ooo ', - 'ooooooo', - 'ooo·ooo', - 'ooooooo', - ' ooo ', - ' ooo ', - ], - 'diamond': [ - ' o ', - ' ooo ', - ' ooooo ', - ' ooooooo ', - 'oooo·oooo', - ' ooooooo ', - ' ooooo ', - ' ooo ', - ' o ', - ] - }; - - List<String>? template = templates[myProvider.parameterLayout]; - - Board grid = []; - int row = 0; - template?.forEach((String line) { - List<Tile?> gridLine = []; - int col = 0; - line.split("").forEach((String tileCode) { - gridLine.add(tileCode == ' ' ? null : Tile(row, col, (tileCode == 'o'))); - col++; - }); - row++; - grid.add(gridLine); - }); - - printGrid(grid); - - myProvider.resetGame(); - myProvider.updateBoard(grid); - } -} diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart deleted file mode 100644 index 69b0f4f..0000000 --- a/lib/utils/game_utils.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'package:solitaire/entities/tile.dart'; -import 'package:solitaire/provider/data.dart'; -import 'package:solitaire/utils/board_utils.dart'; -import 'package:solitaire/utils/tools.dart'; - -class GameUtils { - static Future<void> quitGame(Data myProvider) async { - myProvider.updateGameIsRunning(false); - } - - static Future<void> quitAndDeleteCurrentGame(Data myProvider) async { - quitGame(myProvider); - myProvider.resetCurrentSavedState(); - } - - static Future<void> startNewGame(Data myProvider) async { - printlog('Starting game'); - - BoardUtils.createNewBoard(myProvider); - - myProvider.updateGameIsRunning(true); - } - - static void deleteSavedGame(Data myProvider) { - myProvider.resetCurrentSavedState(); - } - - static void resumeSavedGame(Data myProvider) { - Map<String, dynamic> savedState = myProvider.getCurrentSavedState(); - if (savedState.isNotEmpty) { - try { - myProvider.setParameterValue('layout', savedState['layout']); - myProvider.setParameterValue('skin', savedState['skin']); - myProvider.updateMovesCount(int.parse(savedState['movesCount'])); - myProvider.updateBoard( - BoardUtils.createBoardFromSavedState(myProvider, savedState['boardValues'])); - - myProvider.updateGameIsRunning(true); - } catch (e) { - printlog('Failed to resume game. Will start new one instead.'); - myProvider.resetCurrentSavedState(); - myProvider.initParametersValues(); - startNewGame(myProvider); - } - } else { - myProvider.resetCurrentSavedState(); - myProvider.initParametersValues(); - startNewGame(myProvider); - } - } - - static bool isMoveAllowed(Data myProvider, List<int> source, List<int> target) { - // printlog('(test) Pick from ' + source.toString() + ' and drop on ' + target.toString()); - final Board board = myProvider.board; - final int sourceCol = source[0]; - final int sourceRow = source[1]; - final int targetCol = target[0]; - final int targetRow = target[1]; - - // ensure source and target are inside range - if (sourceRow < 0 || - sourceRow > (myProvider.boardSize - 1) || - sourceCol < 0 || - sourceCol > (myProvider.boardSize - 1)) { - // printlog('move forbidden: source is out of board'); - return false; - } - if (targetRow < 0 || - targetRow > (myProvider.boardSize - 1) || - targetCol < 0 || - targetCol > (myProvider.boardSize - 1)) { - // printlog('move forbidden: target is out of board'); - return false; - } - - // ensure source exists and has a peg - if (board[sourceRow][sourceCol] == null || board[sourceRow][sourceCol]?.hasPeg == false) { - // printlog('move forbidden: source peg does not exist'); - return false; - } - - // ensure target exists and is empty - if (board[targetRow][targetCol] == null || board[targetRow][targetCol]?.hasPeg == true) { - // printlog('move forbidden: target does not exist or already with a peg'); - return false; - } - - // ensure source and target are in the same line/column - if ((targetCol != sourceCol) && (targetRow != sourceRow)) { - // printlog('move forbidden: source and target are not in the same line or column'); - return false; - } - - // ensure source and target are separated by exactly one tile - if (((targetCol == sourceCol) && ((targetRow - sourceRow).abs() != 2)) || - ((targetRow == sourceRow) && ((targetCol - sourceCol).abs() != 2))) { - // printlog('move forbidden: source and target must be separated by exactly one tile'); - return false; - } - - // ensure middle tile exists and has a peg - final int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round(); - final int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round(); - if (board[middleRow][middleCol] == null || board[middleRow][middleCol]?.hasPeg == false) { - // printlog('move forbidden: tile between source and target does not contain a peg'); - return false; - } - - // ok, move is allowed - return true; - } - - static void move(Data myProvider, List<int> source, List<int> target) { - printlog('Move from $source to $target'); - final int sourceCol = source[0]; - final int sourceRow = source[1]; - final int targetCol = target[0]; - final int targetRow = target[1]; - - final int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round(); - final int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round(); - - // remove peg from source - myProvider.updatePegValue(sourceRow, sourceCol, false); - // put peg in target - myProvider.updatePegValue(targetRow, targetCol, true); - // remove peg from middle tile - myProvider.updatePegValue(middleRow, middleCol, false); - - // increment moves count - myProvider.incrementMovesCount(); - // update remaining pegs count - myProvider.updateRemainingPegsCount(GameUtils.countRemainingPegs(myProvider)); - // update allowed moves count - myProvider.updateAllowedMovesCount(GameUtils.countAllowedMoves(myProvider)); - } - - static List<Tile> listRemainingPegs(Data myProvider) { - List<Tile> pegs = []; - - Board board = myProvider.board; - for (int rowIndex = 0; rowIndex < board.length; rowIndex++) { - for (int colIndex = 0; colIndex < board[rowIndex].length; colIndex++) { - Tile? tile = board[rowIndex][colIndex]; - if (tile != null && tile.hasPeg == true) { - pegs.add(tile); - } - } - } - - return pegs; - } - - static int countRemainingPegs(Data myProvider) { - return GameUtils.listRemainingPegs(myProvider).length; - } - - static int countAllowedMoves(Data myProvider) { - int allowedMovesCount = 0; - List<Tile> pegs = GameUtils.listRemainingPegs(myProvider); - for (Tile tile in pegs) { - final int row = tile.currentRow; - final int col = tile.currentCol; - final List<int> source = [col, row]; - final List<List<int>> targets = [ - [col - 2, row], - [col + 2, row], - [col, row - 2], - [col, row + 2], - ]; - for (List<int> target in targets) { - if (GameUtils.isMoveAllowed(myProvider, source, target)) { - allowedMovesCount++; - } - } - } - - return allowedMovesCount; - } -} diff --git a/pubspec.lock b/pubspec.lock index 12d429b..bdba0ef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: bloc - sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.1.4" characters: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c + sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.7" easy_logger: dependency: transitive description: @@ -106,18 +106,18 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.1.5" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" flutter_localizations: dependency: transitive description: flutter @@ -140,10 +140,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_parser: dependency: transitive description: @@ -156,10 +156,10 @@ packages: dependency: "direct main" description: name: hydrated_bloc - sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d" + sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.1.5" intl: dependency: transitive description: @@ -172,10 +172,10 @@ packages: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" material_color_utilities: dependency: transitive description: @@ -200,32 +200,24 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - overlay_support: - dependency: "direct main" - description: - name: overlay_support - sha256: fc39389bfd94e6985e1e13b2a88a125fc4027608485d2d4e2847afe1b2bb339c - url: "https://pub.dev" - source: hosted - version: "2.1.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "8.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" path: - dependency: "direct main" + dependency: transitive description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -236,26 +228,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -297,7 +289,7 @@ packages: source: hosted version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c @@ -305,29 +297,29 @@ packages: source: hosted version: "6.1.2" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -348,10 +340,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -425,18 +417,18 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1" win32: dependency: transitive description: name: win32 - sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.5.0" xdg_directories: dependency: transitive description: @@ -447,4 +439,4 @@ packages: version: "1.0.4" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.16.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5c4cd59..5c63879 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,12 @@ name: solitaire description: Solitaire Game -publish_to: 'none' -version: 0.0.17+17 +publish_to: "none" + +version: 0.0.18+18 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: @@ -16,16 +17,12 @@ dependencies: flutter_bloc: ^8.1.1 hive: ^2.2.3 hydrated_bloc: ^9.0.0 - overlay_support: ^2.1.0 - provider: ^6.0.5 - shared_preferences: ^2.2.1 - package_info_plus: ^5.0.1 - path: ^1.9.0 + package_info_plus: ^8.0.0 path_provider: ^2.0.11 unicons: ^2.1.1 dev_dependencies: - flutter_lints: ^3.0.1 + flutter_lints: ^4.0.0 flutter: uses-material-design: true @@ -45,4 +42,3 @@ flutter: weight: 400 - asset: assets/fonts/Nunito-Light.ttf weight: 300 - -- GitLab