From 96ebdef4e3e3ada16e5472636e1fe572d0c43753 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Sat, 8 Nov 2025 12:52:33 +0100 Subject: [PATCH] make corners straight use straight lines instead of arcs, add randomized level generator finally, make default difficulties bigger, make main menu buttons smaller, other fixes and improvements --- .../powered_lines/corner/left_bottom.png | Bin 1216 -> 309 bytes .../powered_lines/corner/left_top.png | Bin 1222 -> 334 bytes .../powered_lines/corner/right_bottom.png | Bin 1185 -> 301 bytes .../powered_lines/corner/right_top.png | Bin 1258 -> 322 bytes .../unpowered_lines/corner/left_bottom.png | Bin 1151 -> 295 bytes .../unpowered_lines/corner/left_top.png | Bin 1143 -> 321 bytes .../unpowered_lines/corner/right_bottom.png | Bin 1109 -> 287 bytes .../unpowered_lines/corner/right_top.png | Bin 1195 -> 305 bytes game/cell.py | 8 +- game/level_generator.py | 129 ++++++++++++++++++ game/play.py | 18 ++- menus/difficulty_selector.py | 2 +- menus/main.py | 4 +- run.py | 2 - utils/constants.py | 27 ++-- 15 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 game/level_generator.py diff --git a/assets/graphics/powered_lines/corner/left_bottom.png b/assets/graphics/powered_lines/corner/left_bottom.png index 1b4df807645272b8dab190791ce20b440570a816..7062a16c7a15957e711c17274d62c04fdec9b7ba 100644 GIT binary patch delta 283 zcmX@Wxs_>xay`QzPZ!6KiaBqu8TK7A5NS!YKc1j8-Q*6x%Sp*1cCTs7%Whn1aoOZM zoqNOP#U7t!AKcvk$YNun#GPrC-`MsoWiVsipxNNdn8TG2%rKkjhRA_saG}}!9BI6} z9J1Nw%6q+Bz+Dl)@Uyd*@5G9~`!1dF<>#Mb zn}3JaUz{VH@nu)(i{)x1u?KzsvorqfxM=oYe+k?%Sl#!7L8PeP-l*+`#}Wo0@O1Ta JS?83{1OOJle`f#y delta 1197 zcmV;e1XBC80>BB7BYy;kNkl8q$N;Vp*OuMg?@$J)!gMX2q9Owv0xB_se$A|x{)N*N=>xdx*IgU4zaGf^Wx4t zrxyn&y{I%XGdnKK|7zZtIluF~o-<@{a1d%)H0S}mt3rUF6@LN*tq>q+g#bY-1PEFo zK+p;Sf>sC+v_gQO6#@jU5FluU070wv0SIA)u%L%=bcv250075uVHhKfk)X%+MiN!E z%otdf>^MTD5<#SdfFRhSXbXY^#~z00x&i=(p;(r*vlHLiYS`Rt+}LQ+w8Y+C3;?3h zeNnWJEe2tS_qy`0W1s24lo=1VR{(1ORyPqV4hHPiJO6VvNYLc}$#& z+_>=rA*d*(q8Rab>EuZ*m0C}wR^#y!e}XZBNGU~5Sbr#_A3V6cxY#bsrXVatAJ3<%TArHv_~y;;N~O5V(AoL> zwQHk>q3|nJ(_3c`Zi-lJcV(qzXy|)QOSlX@J(In?6Xmk%*1js}i0O}K*itEe>(;m1 z+x0F(fB(;?PyeOsbyc&&(epPVl_aZFir>EdjcLlfhER$F13$%Ld$uiBXPxv;1&D|B z@_%ycojYIh;Ud!d`t7S%$1F>#0RRAS93d9lou2N>=X0(joj?CuHal0HfcBsTK%{kb z`u+R;YilV=k=KE~zHw2s8S}lx@ZbZyB-?i6-o4NGF(OT+*1EfYH4NErBX~1Ih*Xx1 zg@ulpnNGJD=W=;Tvi$DzJbOE1BoeVFCVzSzN8pZ>NNAbNyk+@o1TO)iQEO%8)U#*j z+&1aLg{OXZM;_akQs>E&PuxZ|oh~Gk>y=8xzm)1F055GzORX_nVtn{qrL4lQgAhS@QDo2XL^OOQlv3Ys7`P7=yL7X7{wQag)*-31Tim zz*n!{hl34)hK9dIvEox4dk?@E5d>#v=LBDW0zfRbcd-BEy&Zh903m3aGT%@FfM|3- z5~=WQW6$M_1t>++lnq1Sr;=nTiY9?5c>XQ$?-aiu8{tK=ON$D=oh?;HryA> z+vfRvy5OanMRKp(*&20DKWY=Y80CB}b>4hU*>gJ|d%XUoq1ql0n);@&?#-R=h77%o z89WZj3{uP&BpQw}n6NE?3*CCkyr`ouR^(z`@7%xjD%Yfr>3z#zd^p{{({lR7*|(y0 z-2L|6rp4m?`HkI5epfqRn9N<3G*|Gqc!BcT%Fhg!OW)sq{dITl_s?_ozopr06)u-X8-^I delta 1203 zcmV;k1WfzR0>%lDBYy;qNkluv%ns4eux5C|nE z4JIWLnqGLJO_P%N0rW%gA@IIQ>xJct#8g`h2{BD56dF{BLW@+JWd)W%%f8H2><~-0U?ZdJPQC?U}K|(bMr6_Apl4uGG2Rm`AuyS z1pZ%FDYPuNp?~2y0O(#{T1o}CBKa(Us7fU>9?vEcF9Co^mP#z0PMIbTVgf)A0&)e1M8cO_ z?0RcouA&qwg}uGsFeaoVBALy`$Hza2L<)g+KlmbG7$TR8b#;xNKK)G3T>!X$zdN`R zdONQxKz}51x!AdLbKTv;dZ{6jPoKU$J>43O=7a8jcufH!>AH4p?b?+qgG5q~{(L@q z=gudL1ywt|nqmKElH}YpP2Sh{Wlhb7?rcOgG<11&wJx~g=hYbdJdLF5TBcdLcI}H~ za!IErk`EqyI6Zy7JcCEW_HmA3h4J^Wt*y~1p6 zR-ZXDfA#9u-pC=63k!|6Z+~W4Jdl{X^Irw@XA;M;0r29*kxQ4pBa++FH{H7RIgvz^ z)zI*~r{~+Irk`PJze^+^J$i3&@KZ!(7^2MSVGjYFTPft+)Kyw% z=YLp7#}D4O2K|y69qk$(?qQ7UWwd<3w_sk}?G2r~R5FP~DwS?)n`~{J(%*T!^toJY zXz238hwn$DdDz;%geD+_p%e*0Qj*@4$K$JwjnA5!XPcUS(VeXeW8I#eZMl2*y zVvI9Z(yxMpFxGd2Wim+sK$Jw1Nb0{5u(eA($k8>-G+o`&Sr6to*#ma)`*?2-S%3eN zia^*@1j42w5H=Nou&D@yO+_GVDl7tFQxOQ8ia^*@1j42w5H=NousQSse*za;H)<+i R-u?gp002ovPDHLkV1h!&K3M<& diff --git a/assets/graphics/powered_lines/corner/right_bottom.png b/assets/graphics/powered_lines/corner/right_bottom.png index 533996768ece1f5406b6df67ddd31b9b1dda5aa4..42f564ea241aa47ce3a6bae577cdaf20cd1e00be 100644 GIT binary patch delta 275 zcmZ3;xt3{yay`QrPZ!6KiaBqudUG9e5NLV$%0M{YJMd#pQ=F>%#nyCTMZH?LOL{Fj zcV}!9c%}Y&UX5g0QfTPI`Pw)1KjboP5N?>u@P;Kpoxz&%2Im1khB6c(yR>HuKQk^_ zuruwRNu|xcHP?3^e849m(A3GqAkn*S^2y5jjTXzk^hB$2J;>_)b(|q<7$N} z_O`>bcel=Kad5aTc6a;z_u-|s{x2E7?2&F{U`YFCcIE4vf{1BP@9uqGtuJb63yEGw1Lhd%aC@n7*#%2HSp9 zxXdnz;{t%l>h&O{QaYWxa^=rdDla}~3>ia2hR7s|?!Cn{6~@qe075wDMAsvU#QU+a z-!5Lviq9#P2;ssy08}cGjg6j-jWgTZUzbX;YE`S%LVw-^AhLR0ktAz;{MXUZKV;bw zsba@_XXmS2E}hS(3Wc6ZC9;=FWRaHuh^$)G;_CB% z%NKM#jK~6khN74v?>Q&-BAx*tGF=Z34lYhjJy2Cc>`fwDR#whuG9$~&7buk!#SDja z&WZi{Xn!4V00`lgO62q8NHw<-h za&CAyBZ4Xd|LWDZ4#P10b?WB{Dhr%kc11JMfI*qenkJdo~;h z)HO{%pmm|P0*JgH8+*LhyZ zLjW*_v$J>C*Atp{bobC60FgY`uKk`!tlP;mKR=PpUTP)w|1Iv2$uCMI&)Ku@M@BMs z@_)R3{Y@q_8j0+-=pNbwK&f=&#)2$cVp1rVqjPh&g2DR1d*wcI7XXnNMyS7kDV@&Q zqx{0cc&QW>bDIN?Xnl!&MF8OH)u#X;X3W*qbBl{ZnpQn*zyFe41R#W)W-yst>+4&w z=Y=OveqfB)=L@aNT>#6HFJJyk3^s)D)qmA<`FzTrJspu-0EBQ#stwjb&>tWVl1CesjBH<_`&5OCaF{on&bYPHv_sJMx_6D*Wu(A z0ArG->Ak&c(EOj3&!@y2XXOe2A-vI$ySukzv7+cdx*jeRdV;}*BfWpG+ybyH`G553 zP0@baSfkrf1 zhGqp9i!n)ZzJkHMUqF(mqL|R!!fLe;B6qw1zzqOVxQb$iLN#a}qSR_3$9DiB7k6U2 gWRGyxaB%$#+*~D};hKmn00000Ne4wvM6N<$f&q~=t^fc4 diff --git a/assets/graphics/powered_lines/corner/right_top.png b/assets/graphics/powered_lines/corner/right_top.png index efcda30ef482f54a7bab2f12e01da56ad28109e5..3cf48e6bc11ef59606d1b5938c4951544c7de579 100644 GIT binary patch delta 296 zcmaFGd5CF(ay=ubr;B4q#hkY{4f75eNF4q6zPM}B32EnQOSgo@=_e#lx!tVV&6A_( za-vT5mhuf7*Q)=_HP_Bhn|#oYr&;u@tfr}<-}1}h_s^%9o&WpukEQR%OrZy<)45ZR z@3mvR!Fj-sp^RyRaKl`NH!KP24A!VZ_p<8s87i*Ze*67?PV;WY*5~2uGiF+EyB)ps z;Zw$j_6gIM?v3d)yjQ+^{<`WXGIzZ5_szU&&3NGWN9AKL-@6``ea*b(?WCaMy>d<` z8Y&+ypZQAKz`iPzrlY)bLtQ1r4%3HLovkQ^iqmlVw{2!JHhfVj9N?nrMR-C z-PP{S^ziDXw57Q5u2u$pPotGIGoN|y&3pUE;NT!QDnhtr34g(0<>AB66-5OARaG86 z`m9n33WCKsadtWC>~#MsLO3DZrOBNK;G9s(bX`(a1wTrXt|%&Fg5zs}dloQFpRP*) zz&Rm=%d*B8al;O-01(1WQ{3JT0RTFEB(lYrD{Hw0U|F8#Ov#= z_Ba+RQp(&|#eXdTL9mL&7!Cj+kyr@^E0*Pa9J>O*7!^fhZLPIj4imyTC*g289xoV% z=(rtRv_PeDD4%Z!06h3qYVN>T0L3}UKGmU#CD{^hkw6w z=bLc2jM@6_X08(e0AQd44IHha!y zICA98y?bAEb!B&ULICi8-KnksC|TBqhi|-i(PcBpvUcm%cm4e%j8QB!_I|_G8vvUW zf?y5}eTu$>RR-te(xsmsJoqA+T&z}uhT-2Mh6fg~8!aOv15cjxa}Ja;&Ix1m*s*RKz^w3Ij}n5_Z8vV?5*+|<vR)(=(M!F8F*V{)RD%)i|Y05G+Mev3K7P04UsFAW&tDPJd6I zotZh)+Paob&t@{SiNuO5Yk$je7{m<(fIr)d!w_pV|MqqWfHX9KEt>>|=};MS#lZmhr)(Y%Y9VLU1n^o-0I$^q@LEj(uhj(b zT1^12)dcWbO#rXe1n^o-0I$^q@LEj(uXW%7{s37nMc@2gYEb|H002ovPDHLkV1jEg BT)Y4P diff --git a/assets/graphics/unpowered_lines/corner/left_bottom.png b/assets/graphics/unpowered_lines/corner/left_bottom.png index 7e01c78bad3185611a4599cb3e11ed95b557049e..c6cb296784740b31d2d3cece9731feee4676b418 100644 GIT binary patch delta 269 zcmey*v7Bjway`R4PZ!6KiaBquUF2*r5NLUr!rOJjflEYw?LnuUi^q>@WV}1e_U;*5 z;yxy`pN4LaH|ab)+q1VZU$eoNF^4N5m|-^44Uq%O7|d8Vz=dwVW)4~_y33(Q(8}J_ zmpS&qzWf(|p1ixjx@(;=v)q*%-)-}%I1*uhiO wa+ltnFZLcU+NwdJV|3A|J5`J z&GVb*Z6<>b4-cW8g`GCwPvrtQEf>IPxd2Yf1#ns}fYWjToPU-J;Iv!-r{w}TEf>IP zxd2Yf1#nvJ4?roUlsaw3_vPU@4gl!C1ONyjgpkv={gOP2q5uHT^Bl){y$+hW@Z({SAP4{;2!hY&6GgG7r>DQae_&uhlBCYgP5@{$8oI9kSd1@i5=Ai>42Hwu zP$<;f+e-+k*MIAjQs$zcqkjM}#^y8|hGAy5-|r8H!!t88vMh5Pr)e4h{2Xr}K@cdV zYTf3P)O4>!!R6=D0Fmm1OWI`2!A0$h@vR@eE#wA@!{d2P$;lR znL`L60I;yI5R1hGLC`eKSq5})aA5v0qllGM7V+6^HkC@H(`ooDsjOW{lC-h0F+M)7 zD2js&=&M6MW+leh=ko!;^Yio7)fK~F<`P0Uj$2t-iAJNfS`8e^Y|mFW%p6r!6#zy@ zM`c+~r+?G&c)U<3u*5J7dEQP_|6jkC ze5{@-iZVMpySuw9ilWJIcXxMsdMXG4yV5fKc7OF?uBcQh!^6XSdwY^3nGDHf^78Vs zqoc#@eJ#*8%O9(-qA2d{>~wc`n+z8h7f(-5e!stEe)xX<#u(S@bx{I?<8fWrTd>5i;00{`=kGK9n7si`Tm87C5ndcAJ96D<38gpjW5i;IgK$FV>v7K@oo#%?EA z2~e-s1A#y!5;2!asZ`4D)se;iH4I~JZhwwt0sv&Q*?d0l^?L0qsa688))ou~1Azdg z)Z_6OhLOo++6DjsD5YMnHxh}MS2CFl`>bT`i%eMzz*0FB3So?OT?c@dmzPqhWZ%ld zdVprLDM^wfNdSN`ZZsOXT+Y5Ivc5{rz)OUB@8#44VX7D&9 zGe|LCkZ3r@V8XTlE_CZPbIkkNcS-+^%fCsdubsQQ-r}9x^7D0%i_R5`yE|w;HP=75 zbc#t?_51UmJ37kRq|G0AO-l{r4~WfQ`}Vh7@|SztuPys8V{mLoY3%a;f7^4Tdp1s4 zv_=1UrIs)My=uXW72le<6*YYM*P33f4bKj>b$EZ1-|sg=?)v{?N!T2u0CnV+>+CfQ XmP~%nWhINxG5~?6tDnm{r-UW|hrxmw delta 1123 zcmV-p1f2W90`~}zBYy-wNklf+#_ammo@XA`TQp zagdP+zCs?thwuRe9qC+9L2w`_-bOm`77daMBmor@`&M-ZYumW@R?j(mcc&A`H|idw z`~Oz2>aMDg<>h6thLx3-VzEdF;hd*ZsYoQEC<-CO+`Bl;y?>km=bUqH(ZxSvz5wUk z;c&>ZT&-5|)a7z{JRZiF>GuMQo&XBSvW%yqC{jurMgX6{^YgQ=>kfwl09smF+-^7L z-1Gqe7Ciw$5K5&I0H8o?YilDt0VJQ#8^`VK?Py{J=3kc(q9{rxlK}v%x+F=~y^%~7 z;G9!ROQlkwP=5db#+cLTlq5-2RVxMnSR`U z7kCT+(P)%X+88awITuASo6X+e-=m4c;ppq@v#$JLx`5N^JU%|g=$!M8jt)tZte65B zm?nVTUnmsL&(HA`07geg1wpWMQe>I{W6b4p9UL4eihm*q0%I%?2=w>&%d%|UM*z$c zV2rul?uUnmlamviKLfz{__)vK({^R4i67kDiy2C4}U3u z1Gii*Z+~uXGRCm`y}LZNyWV2p{P zsA<~j>S`{R!}N@?iHV7LJYJjTL7ac^8|?dXxxBuB^aBF}v$M0cOb`E80N*fv zzyJFBdShe5=p4paS6A2K;-admmc;y;|0;mnB!4cKix9H6x3{yi!x%HtcXxL$EiEy| za41_D_*ed20Hae%MN!OTGTYnRH#ax%zTRbw4Gs=2EG%%&bzQGkqpE7{YmYdMxE~LU+`6u3v)R+r)3dWP+~+aU`+UA+GC4Ff zRDY>d;C=aGn}8q)j(2cXUMv>V>Gb90LxLKZS%sAYrv%tS0ASP`00<$Sot+~iBe7V_=kvY2 zy&2=U9{5qk-&cL2C`KZYXf)c>(<4ceAPBN7*Hv`n#}jb7-IP+l-yaAB!r`zaNw}Xw zDb+MhQ54*=wKwahkW?xK0A8<`QtEQKD5bb4QB_sbH2f6QL$7~@GZKja0DA}jm0W=L zRbo9yN1TeH;1Oo6&p!DEXZ_dsdm60Xzta@3o2G!>GzILYDPT8E0lR4m*iBQwZkhsi p(-g3qrhwfv1?;9NU^fjf@DB&MIBeLC0h0g#002ovPDHLkV1h;I7B~O^ diff --git a/assets/graphics/unpowered_lines/corner/right_bottom.png b/assets/graphics/unpowered_lines/corner/right_bottom.png index cc058a06aac532001b788b9263e6ae6bd1eb0d12..db9ff7ad3b48cf9d59d05e6ba47d8d594650d7b2 100644 GIT binary patch delta 260 zcmcc0F`sFIay`Q{PZ!6KiaBqu9^`B>5NLfkbAhTx#O$*XUDKW)TzpaG(HqAJwr^8q zlp-ZZp)3mUOBJ-@dxXpwb~A$#|vYoJ+<)mdKI;Vst01sPp1ONa4 delta 1089 zcmV-H1it&90@VnRBYy-ONkl@{*?Zo2%$C>WJJ;`!j&>D1hs`jQ2t9>0RUqR0E7_u{s5Fx03d|S&CL-)5JHqv+qP}n?smJ3 zv2O~%;~fYDLZJ`74=NMy*F{PAJ>Nw8$UxL9P#@KrR#u!3KmgRcAesgnEDwX(iLI`7wTL%DHmK8;b z#bQfKOVMaF91e@3=sf_XG#CupwtaPVb$54X+cr;?QGdJ_7Z+DoS5;M26h)ThajJh7 zy0`Ab;jpgj=jZ3OS`7d&#*8s;J&{OkZEYo!NlB6@rGvr1vMiqW2%*s+o&iuwB}pn4 zi`i_}G)=xY@oY&X61%&*$z+lcVp*197-&p?jN=UeV@#Iid_JGc+45HN0CT`Z?U{+{_kkB2B4J6 zvV3uIQ79BffyWph9v<%O?DYG6!!SN*ool85Jn!@Q{J8s2D3r-$QmIs@)8QrPQ)A8! zfY+R2v6#!{M($-xrdnn zAcP#pIXgQuO_Kv)j5C?cgzjNB06ckaZ*N~-UPj4taBz@Hr6zO_GXUVpQ?J)~(F6cW z>91eE_V)HVoz8^rVKxASkn8Jf+qQY9u&}Uje0<#R_di}M{~~t*D5at(mdj;L(?+d) zI)9yBSXkhF+vHQY3&1TF3Izb*J!U)}-`w2ncDtXg_x~jq0T^R~AXKZ>N~JRD7xwq} z`Ky@LEbamX0)fZJN8Z>l#^Uj~s;Z;jb1HHRfV*!to8@vDhL=#=+uQs^GR28p1P}zF zQmOFfj8YnnMpad{ENhD42Uk-FA*5*<41e4GwY4=#lKwcDPcF9r2qA`HRI61Op0iX{ z$7>pBeg zvREuO_4>o*767Gm{5ErWdD#v3;1U4FcrX~W+ie)G;Ak}J#tH`a-hkscmSw?k3p(Rh zwo_jK;06F+xRz!0dOa9kqKKk6^&NoA#hrMR^k2AZ__%%p3r0!(dO1Cy00000NkvXX Hu0mjfh(Qb8 diff --git a/assets/graphics/unpowered_lines/corner/right_top.png b/assets/graphics/unpowered_lines/corner/right_top.png index 7c09a07c71581df8f5f51079b86bbfc8137cd6d9..bb5de31570e285b666ba573c43ebdc50704fe576 100644 GIT binary patch delta 279 zcmZ3@xshptay`QjPZ!6KiaBp@Zd}`9AkdQ7|5v5u#6l;Im5ow*s!QyRngtL2ZpuEm zPw>(o);F)1C7F^V#llUDxfPE{*v~&-Iq%%gZMn}Y-|c!=DaVj{{pY5~b@LhCuq3E6 zSTo+>JmAMr#jBy)qnB%7gv3b|F4W;E0RNYUuXWuDDCJdCh*a`$&CRBJYD@<);T3K0RY>n Bc!K}{ delta 1176 zcmV;J1ZVrP0;>s-BYy;PNklw*^T zT=Y-$H}p5Os}^mdU5f}3qD{C6L4p<`S{6a5Wu#d;Gmdlbvp7%k@Z)*u`8CrR_+4FR z8EictWXrj3k@-~a$h zrBbumG$N0UiGNEOT z2qBS31OTvl=5je%mUXX1Mgb5)+U<5Y9Jbr-nx;`otJP|;ShU$}`bLp~0II5bJf89K zaR9)L&tx*XFJx>l#2A~InE?Q7;|hhs-QAttZr9NX27dxjN)<&3g+hTq0FO4#`Ptc- zzG+~r2e7qXTwKI4AtayAUtL`d4i4(&1Y-dxrII8~OiToWLB<&FeJYibWmymeU3xpi zzXx#RmzS5Z&8Cz-KR>5ZDNz)E1^}g0mgR6bJU2Io7E?+ynM^jDbvm8cXnbjYJ&gPh z5ke$MT7Ozv8X6kH&?kf(9vN#Lx8fbRv-$9UWziDT;zs>!0of%mF|##vBgE&CSih!NJSR3tEW`f*{1>@!8qg zKz|@$u~<}9?Q}ZbGvrUx2{9P}02pJUD89YDot&IxvsnPZaOzg1U@#bsMnj>H*Xy<0 z?Es)@8e001z?@ER|d%blK{K0G|Y2Szsy2qDA6!+}7+@ArGXUbowAx7)2& zE2VV6T7w6~am48YF-{L)hrfHQ*<*yH|R;wtAJplj!82yqY zQA+3M=clHo3WY*8n=KZLolXb;Ln-Yhx~6H8B;nnCPXM6MzE-P6DUHQq(P;GP=_#Mj z=kxhesU*wtFJ~MkaSs8Y5JQqALWtMv_51yaM511=S1OfKsr2~xSgY0E-`^EQ!G8+d za{wsBP*t_vZWBV>ZnwwdiN#`^b5&IpMNw6?)oS%F0IGX_bvhkYRl5%@7K_ztb-7%G zke&wU>X(z4bB?JDJ(vTZRF}tuZd70O0nAn(z-;va%vK-3Z1n-mRv*A@^#ROQAHZz& q0nAn(z-;va%vK-3Z2fqEKL9w@MG size * size - 1: + num_dead_ends = size * size - 1 + + grid = [[set() for _ in range(size)] for _ in range(size)] + all_cells = [(x, y) for y in range(size) for x in range(size)] + random.shuffle(all_cells) + + start = all_cells[0] + stack = [start] + visited = {start} + leaf_candidates = [] + + while len(visited) < size * size: + if not stack: + unvisited = [c for c in all_cells if c not in visited] + if unvisited: + stack.append(unvisited[0]) + visited.add(unvisited[0]) + + x, y = stack[-1] + dirs = list(DIRECTIONS.items()) + random.shuffle(dirs) + + found = False + for d, (dx, dy, opposite) in dirs: + nx, ny = x + dx, y + dy + if in_bounds(nx, ny, size) and (nx, ny) not in visited: + grid[y][x].add(d) + grid[ny][nx].add(opposite) + visited.add((nx, ny)) + stack.append((nx, ny)) + found = True + break + + if not found: + if len(grid[y][x]) == 1: + leaf_candidates.append((x, y)) + stack.pop() + + leaf_nodes = leaf_candidates[:num_dead_ends] + + for y in range(size): + for x in range(size): + if (x, y) not in leaf_nodes and len(grid[y][x]) < 2: + dirs = list(DIRECTIONS.items()) + random.shuffle(dirs) + + for d, (dx, dy, opposite) in dirs: + nx, ny = x + dx, y + dy + + if in_bounds(nx, ny, size) and d not in grid[y][x]: + grid[y][x].add(d) + grid[ny][nx].add(opposite) + if len(grid[y][x]) >= 2: + break + + return grid, leaf_nodes + +def generate_map(size, source_count, house_count, cycles=15): + conns, dead_ends = generate_spanning_tree_with_dead_ends(size, house_count) + conns = add_cycles(conns, cycles) + + houses = dead_ends[:house_count] + available_cells = [(x, y) for y in range(size) for x in range(size) + if (x, y) not in houses] + random.shuffle(available_cells) + sources = available_cells[:source_count] + + grid = [] + for y in range(size): + grid.append([]) + for x in range(size): + if (x, y) in sources: + grid[-1].append("power_source") + elif (x, y) in houses: + grid[-1].append("house") + else: + grid[-1].append(classify_tile(conns[y][x])) + + return grid \ No newline at end of file diff --git a/game/play.py b/game/play.py index b2cdaac..f234bfc 100644 --- a/game/play.py +++ b/game/play.py @@ -5,6 +5,7 @@ from utils.preload import button_texture, button_hovered_texture from collections import deque +from game.level_generator import generate_map from game.cells import * class Game(arcade.gui.UIView): @@ -20,8 +21,10 @@ class Game(arcade.gui.UIView): self.houses = [] self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1))) - self.grid_size = list(map(int, difficulty.split("x"))) - self.power_grid = self.anchor.add(arcade.gui.UIGridLayout(horizontal_spacing=0, vertical_spacing=0, row_count=self.grid_size[0], column_count=self.grid_size[1])) + self.grid_size = int(difficulty.split("x")[0]) + self.grid = generate_map(self.grid_size, int((self.grid_size * self.grid_size) / 10), int((self.grid_size * self.grid_size) / 5)) + + self.power_grid = self.anchor.add(arcade.gui.UIGridLayout(horizontal_spacing=0, vertical_spacing=0, row_count=self.grid_size, column_count=self.grid_size)) def on_show_view(self): super().on_show_view() @@ -30,13 +33,13 @@ class Game(arcade.gui.UIView): self.back_button.on_click = lambda event: self.main_exit() self.anchor.add(self.back_button, anchor_x="left", anchor_y="top", align_x=5, align_y=-5) - for row in range(self.grid_size[0]): + for row in range(self.grid_size): self.cells.append([]) - for col in range(self.grid_size[1]): + for col in range(self.grid_size): left_neighbour = self.cells[row][col - 1] if col > 0 else None top_neighbour = self.cells[row - 1][col] if row > 0 else None - cell_type = random.choice(["line", "corner", "t_junction", "cross", "power_source", "house"]) + cell_type = self.grid[row][col] if cell_type in ["line", "corner", "t_junction", "cross"]: cell = PowerLine(cell_type, left_neighbour, top_neighbour) @@ -81,8 +84,9 @@ class Game(arcade.gui.UIView): queue.append(connected_neighbour) for row in self.cells: - for power_line in row: - power_line.update_visual() + for cell in row: + cell.update_visual() + def main_exit(self): from menus.main import Main diff --git a/menus/difficulty_selector.py b/menus/difficulty_selector.py index c1d93ab..1be2dc9 100644 --- a/menus/difficulty_selector.py +++ b/menus/difficulty_selector.py @@ -22,7 +22,7 @@ class DifficultySelector(arcade.gui.UIView): self.box.add(arcade.gui.UILabel(text="Difficulty Selector", font_size=32)) - for difficulty in ["3x3", "4x4", "5x5", "6x6", "9x9"]: + for difficulty in ["7x7", "8x8", "9x9", "10x10", "12x12"]: button = self.box.add(arcade.gui.UITextureButton(text=difficulty, width=self.window.width / 2, height=self.window.height / 10, texture=button_texture, texture_hovered=button_hovered_texture, style=big_button_style)) button.on_click = lambda e, difficulty=difficulty: self.play(difficulty) diff --git a/menus/main.py b/menus/main.py index d533a83..a15ce0e 100644 --- a/menus/main.py +++ b/menus/main.py @@ -52,10 +52,10 @@ class Main(arcade.gui.UIView): self.title_label = self.box.add(arcade.gui.UILabel(text="Connect the Current", font_name="Roboto", font_size=48)) - self.play_button = self.box.add(arcade.gui.UITextureButton(text="Play", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=150, style=big_button_style)) + self.play_button = self.box.add(arcade.gui.UITextureButton(text="Play", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=self.window.height / 10, style=big_button_style)) self.play_button.on_click = lambda event: self.play() - self.settings_button = self.box.add(arcade.gui.UITextureButton(text="Settings", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=150, style=big_button_style)) + self.settings_button = self.box.add(arcade.gui.UITextureButton(text="Settings", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=self.window.height / 10, style=big_button_style)) self.settings_button.on_click = lambda event: self.settings() def play(self): diff --git a/run.py b/run.py index 8b9eb44..87ff882 100644 --- a/run.py +++ b/run.py @@ -18,8 +18,6 @@ from arcade.experimental.controller_window import ControllerWindow sys.excepthook = on_exception -__builtins__.print = lambda *args, **kwargs: logging.debug(" ".join(map(str, args))) - if not log_dir in os.listdir(): os.makedirs(log_dir) diff --git a/utils/constants.py b/utils/constants.py index d60fed6..d4a3334 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -7,25 +7,24 @@ ROTATIONS = { "line": ["vertical", "horizontal"], "corner": ["right_bottom", "left_bottom", "left_top", "right_top"], "t_junction": ["top_bottom_right", "left_right_bottom", "top_bottom_left", "left_right_top"], - "cross": ["cross"], - "power_source": ["cross"], - "house": ["cross"] + "cross": ["cross"] } NEIGHBOURS = { - "vertical": ["b", "t"], - "horizontal": ["l", "r"], - "left_bottom": ["l", "b"], - "right_bottom": ["r", "b"], - "left_top": ["l", "t"], - "right_top": ["r", "t"], - "top_bottom_right": ["t", "b", "r"], - "top_bottom_left": ["t", "b", "l"], - "left_right_bottom": ["l", "r", "b"], - "left_right_top": ["l", "r", "t"], - "cross": ["l", "r", "t", "b"] + "vertical": {"b", "t"}, + "horizontal": {"l", "r"}, + "left_bottom": {"l", "b"}, + "right_bottom": {"r", "b"}, + "left_top": {"l", "t"}, + "right_top": {"r", "t"}, + "top_bottom_right": {"t", "b", "r"}, + "top_bottom_left": {"t", "b", "l"}, + "left_right_bottom": {"l", "r", "b"}, + "left_right_top": {"l", "r", "t"}, + "cross": {"l", "r", "t", "b"} } +DIRECTIONS = {"t": (0, -1, "b"), "b": (0, 1, "t"), "l": (-1, 0, "r"), "r": (1, 0, "l")} menu_background_color = (30, 30, 47) log_dir = 'logs'