From 1b31955e9ce45e0da402c1685ab1b2ea599a6c65 Mon Sep 17 00:00:00 2001 From: nehoroshevaa Date: Sun, 24 May 2026 21:49:38 +0300 Subject: [PATCH] [2] task2 --- nehoroshevaa/docs/empty.txt.png | Bin 0 -> 15709 bytes nehoroshevaa/docs/large.txt.png | Bin 0 -> 14411 bytes nehoroshevaa/docs/medium.txt.png | Bin 0 -> 14538 bytes nehoroshevaa/docs/no exit.txt.png | Bin 0 -> 14790 bytes nehoroshevaa/docs/report.md | 174 ++++++++++++++++++ nehoroshevaa/docs/results.csv | 16 ++ nehoroshevaa/docs/small.txt.png | Bin 0 -> 14167 bytes nehoroshevaa/task2/__init__.py | 0 nehoroshevaa/task2/builders/__init__.py | 0 nehoroshevaa/task2/builders/maze_builder.py | 4 + .../task2/builders/text_file_maze_builder.py | 67 +++++++ nehoroshevaa/task2/experiments/__init__.py | 0 nehoroshevaa/task2/experiments/benchmark.py | 60 ++++++ .../task2/experiments/plot_results.py | 25 +++ nehoroshevaa/task2/main.py | 66 +++++++ nehoroshevaa/task2/mazes/__init__.py | 0 nehoroshevaa/task2/mazes/empty.txt | 10 + nehoroshevaa/task2/mazes/large.txt | 50 +++++ nehoroshevaa/task2/mazes/medium.txt | 10 + nehoroshevaa/task2/mazes/no exit.txt | 10 + nehoroshevaa/task2/mazes/small.txt | 5 + nehoroshevaa/task2/models/__init__.py | 0 nehoroshevaa/task2/models/cell.py | 14 ++ nehoroshevaa/task2/models/maze.py | 51 +++++ nehoroshevaa/task2/models/search_stats.py | 34 ++++ nehoroshevaa/task2/observer/__init__.py | 0 nehoroshevaa/task2/observer/console_view.py | 64 +++++++ nehoroshevaa/task2/observer/maze_event.py | 17 ++ nehoroshevaa/task2/observer/observer.py | 5 + nehoroshevaa/task2/observer/subject.py | 19 ++ nehoroshevaa/task2/solver/__init__.py | 0 nehoroshevaa/task2/solver/maze_solver.py | 43 +++++ nehoroshevaa/task2/strategies/__init__.py | 0 .../task2/strategies/astar_strategy.py | 61 ++++++ nehoroshevaa/task2/strategies/bfs_strategy.py | 40 ++++ nehoroshevaa/task2/strategies/dfs_strategy.py | 39 ++++ .../task2/strategies/pathfinding_strategy.py | 4 + 37 files changed, 888 insertions(+) create mode 100644 nehoroshevaa/docs/empty.txt.png create mode 100644 nehoroshevaa/docs/large.txt.png create mode 100644 nehoroshevaa/docs/medium.txt.png create mode 100644 nehoroshevaa/docs/no exit.txt.png create mode 100644 nehoroshevaa/docs/report.md create mode 100644 nehoroshevaa/docs/results.csv create mode 100644 nehoroshevaa/docs/small.txt.png create mode 100644 nehoroshevaa/task2/__init__.py create mode 100644 nehoroshevaa/task2/builders/__init__.py create mode 100644 nehoroshevaa/task2/builders/maze_builder.py create mode 100644 nehoroshevaa/task2/builders/text_file_maze_builder.py create mode 100644 nehoroshevaa/task2/experiments/__init__.py create mode 100644 nehoroshevaa/task2/experiments/benchmark.py create mode 100644 nehoroshevaa/task2/experiments/plot_results.py create mode 100644 nehoroshevaa/task2/main.py create mode 100644 nehoroshevaa/task2/mazes/__init__.py create mode 100644 nehoroshevaa/task2/mazes/empty.txt create mode 100644 nehoroshevaa/task2/mazes/large.txt create mode 100644 nehoroshevaa/task2/mazes/medium.txt create mode 100644 nehoroshevaa/task2/mazes/no exit.txt create mode 100644 nehoroshevaa/task2/mazes/small.txt create mode 100644 nehoroshevaa/task2/models/__init__.py create mode 100644 nehoroshevaa/task2/models/cell.py create mode 100644 nehoroshevaa/task2/models/maze.py create mode 100644 nehoroshevaa/task2/models/search_stats.py create mode 100644 nehoroshevaa/task2/observer/__init__.py create mode 100644 nehoroshevaa/task2/observer/console_view.py create mode 100644 nehoroshevaa/task2/observer/maze_event.py create mode 100644 nehoroshevaa/task2/observer/observer.py create mode 100644 nehoroshevaa/task2/observer/subject.py create mode 100644 nehoroshevaa/task2/solver/__init__.py create mode 100644 nehoroshevaa/task2/solver/maze_solver.py create mode 100644 nehoroshevaa/task2/strategies/__init__.py create mode 100644 nehoroshevaa/task2/strategies/astar_strategy.py create mode 100644 nehoroshevaa/task2/strategies/bfs_strategy.py create mode 100644 nehoroshevaa/task2/strategies/dfs_strategy.py create mode 100644 nehoroshevaa/task2/strategies/pathfinding_strategy.py diff --git a/nehoroshevaa/docs/empty.txt.png b/nehoroshevaa/docs/empty.txt.png new file mode 100644 index 0000000000000000000000000000000000000000..743138b7b778824f6de667e84983eead8365c80a GIT binary patch literal 15709 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 ~W-1)>$(Ai@* z=r|DIh@J%Gp!9)@!-%TD)^TsPT2e$dB;CCy?X@CrK-7l z{d+k7Wq=@kDy%c2yYmx!b99eAP07-kMNZzH;r9u_>@OWzJ$bK{jE4{{HTTVR2b=bS# zX)ZT-e*)XS(%`_rJcY0}XhTY|8tElt#BgyA( zhizEoEOns$z_GoZz2Md{iBFllJfivGF72HM z`ML~!@#(EpNC$F-&C0JXj#Gkz6$l_zXxNFw$e-P&E*jv_j*m6Q6v3+M(h6#IQg#J#%2)c{-PWW3mH;(S<*$psYoRq>d+? zlAT-HUa2tJQ}kgQAjkFF`w6?v7Rj~*(&B)Oy!-$>b?PWcgAc%r&XM8^lVv?@D{+ZK zUa><|2z};1e*Aa=u*UWM`}ZkTbCu1#pP?LvN>8ZG!EWpuX{j45Ntt4qQ7I|er6XwfRYmUz-1+}v_i5>)))&+ zgn3Z0lF==GT-N=gFkq;wk`h>D#Cz3{W75*r3R_t14ARGx_%7M%hi}v|u$N=rtd!#) zp)exm6n!xDq-}WorcUT%6d=lmm5Bm*%8%kXQfCz>x)uC_*JH(PijqDeNJz9P8)vWq zon6tDtGTcrY@Nh`XWBbWfjwI7^C7e?-{ZksZp5UKB>FW?(zBW}nxTq5d zjSjyD`_z>AWQ>lB_=rZZBBVCw2H8r?8l(AIF`_0#mtmb%5BryU@2TXMBTUF%_Q@Pt zSH|g+sI{O#mu_GNyetdrX=w{7wYwTqkkcJU%W%5FDjkljd@+W&Tt+(1Pp1a_PK)|H zQr3{CimA2P@>=CkqSlVmsl&~8+gY^GPT|x~dD3Spr^b!Ikiyj(c%^oSls8R%-_+8_ zs5{qgd+TSzDw=IDSa)6^KT|^O?&s&z8K5p@NV95U1Yrb>cqwSZ3YmNk-VSwpBCn%; z7M#BcvvSvW1ROl0#vekEa)Hc%gd=xg!FG@wfG-5^u+w*Yg+%BMgk_?MwW3W(E>j>_lU0j2=B9+L zWV(tQzV+#G;_xR?*i>zNGr?WKARW?RD1s`9qAdd3z)k`QfMOK#<0SW=DYz*yq?0i7CQ5kqI^JqWU@vW}z$xD4M}waEUj4-6?fL14H~ zR7WWWx~%xE5YB5L3Lr$5^gPrv5dticzR2cLk znM1%(n~*dH4cyZ;HV7dr=%@xE>e;|qJ>0H>Pu)pM+zqg7U|aaL#Vj7xJmqU%>F{CY z*fN30JnWJ!1?w{Vo$xt@IB7a9X1Z-f%z|t(oAc6qeSU_& z`EvZz*H1(%pPnILJs^$ImJIdo>b8MMdh7JkZdL=(?cPce4JPQ6{;w;HN6P_N`mbwq{nuf zNKGeLjI%`~hn*WFLEvixdU!&%jjW=E^{1+gre_SV*sbA$x3y|EXM{LDN+B$!QHyw` z?Mucuf!WR%p&HBnvxYH=Hzw^OPO^jgy%2y$0eiYI=N7XTlY2@OHGDb!(pj@v%$p^U%9vvOs$*5~d%IaRU@id>QDG9rl<8Zx@8k)8o$y*>o zQX0c6y@;q(n^QwvF71qXkK7LLWIw-sFsV*@cA*X$NxG1Gr;X9)+6c(;f%INZKm2^qAPi(s8smIg9e!R zr-2}MSviPAev!-xRV*pJj5%bk|8#1HP9YA$lIh;!oZQq-c8pWl@{RtCezJe&d~){| zZy-`#HMs3#eSLEFH9aJ+8isj=tGd%GQ%RrTT@t1pyk_r#3*#5b(65+Ud2vD*OgVie zwp9P?$(hnp=vO4nO<=9r;ZJ0SvwgVXt_1-)&E-av^xw0P%f)Fqxp(idS1Qd~h(qk` zA#x+%xMRz*gZH&GvG3mNTGN#+ysZJQ7*>jsRQUR|3j@sK{Q}AO*-ZwP3Nu}wi}w_+ z>s7o=jXf&uFcz>moiiuFsl$*lUQ%4@eYN{89dy3eahQ*Gu#3}s=TO58Mnx`>Y`~9v z`UwOXfoa8BinRq+i=2X}Fnz^Im*AT^DZrwV-yIaCWhX>~DJotY(xlcGk02(iYW^`$ zLEI+_+nraF&{jb^+deo_*Q94K%FxmWSRb0{*=c_MB;B7#r?5$!acbTua2#pkvni`X zBBQe0kJ#KRK{U2_`wbP_waE}$PoF-0hx-zI&&AhbM~wHj6ReP|1#UuU)zYTM{J?A) z*-3W-HSzhV%W@KvEy&7=wPzOWuwe29z9&&#c$8Ekk~>`&4zmjh#?1N9A?KP6TM&4P zx)4)_k*@hs`b*F;oZ9J>Jjm-gpG0r&d;u+*DtIF=VaFwnSO;rSUHhUtJlX}Ufn+&S zhcnZdRELvkj`v+YKl#ekGpSV7oDb7qUmuRQ3EABl?2f!gkElim3(khjbJ>riOHUxD zl#}nuc}~^!hdE|Rr~3Ku&!&&`2H(K6ue_D{#h=ag;@lMkV~APj&b1Gzt2v!>tVryD zOYJs4WT6r|@B6y)w<}B9dch-Y8Tf4=k&&7mnpT3jCnJfOB61!n*)X0wX3)2<@D$c7 zBvvK{qkP%w1!$Nt`c0L*?K;m`-0C@EP&kcZvIJ}9EVo~)?PJzali;96ahD8ZS8skL z>rqZPSl-w^_3;V=cGbKq2=8}Pjgbvj^5F|_ALK(o4*g|^G7JH}j}_k&-eGcuKSwzVUm-(%s4hhjn%m7ZyDyEGu-< z*46bcPEJI`@gKh-?CfGA!gsaZ5Y9q>SH%#AAk4SXe^eh7(y$03E2et+qONBgd4N9d z)ynSfF7c(+cd2|!uB&fF#J*Rj9K4bms^zypdCPlab9+X;F6@-{>W#$EYG0Kg-_XO0 zH!N=+djDN}^{atW;|if_uw>WGY2u~0MO$N>_>zCWbBV>c@q}LvE*>W=-r`ji$?{t! zm6<-QyFmFvHQ7RMsZQZp#b3ftzrTyn0H#av4fH%(uss7DU2Qm9TQ9Ym-s z=?iRH-f{(OxAG&wobsC@dlenEkUi!PXYOMVM1?`tWpw!QoU}j&-5)-A^r-IB zL%OEY^7FH$mlh{lL`*8&CG{koiMeKxk!JpeKOzMAZ{NOcePA%}_LosoXJ)#$-Ws`& zKSX{t>JK4<>z}`TNpUCl<~#I=wUE;^B@)yk1QJG6H~lv&Jh{y7+)2C^Ep}zLddq3a^pCSfx(Qz6 ztQ(ZIAst_4DoagWglc_dXqPgiUF=}UD(w_E)BW+%p#PR5i-f7)ik?<%8s5Km4=3YU z8rlP)O|jCkt3GS95g`gYGT&~xw|a`Q!~z?w0z52bG`TZFZ>evmdTUmED?!RJ zhCVMs@TOGXN+#|5i3FeK3nKExF_QK_7Dj4QZswY{ReIsPR(otT`ql=0S}Hl+rn@3I z<-MaE*G<#t)X2FPXIUi{tW&tev_2KzWg6OM=M` zWiBArqLVm@-`!5bCbd-gZY*L#%KWz6Syq=<+_2s4wLjmb=U(ykt^E4!TYYo<`LXF- zLh9%JmXb_}C#$60o437jX$jsxCfv}6jaPcS-z#UXx2U1eXLBmeua}Ws=BI;Mg}dX6 zT>Ap+rnt#T+X)D+1jpykpKE@3!Wh5*h`@Rwx%*>M_<0W}o828svwd!r^(w)2N*gZ~ zf8N9W$D1=FW&WGX*mh04mYtAc{?|gY5~l%;5|eV*`ZH$LmAJJ5kJyn{uU@&GI&~`U z%Twm9!iI4E5ZU04pS7*EnMOq!IC8HfeuC}r;oldQe_q0nPe|AfFKu^>HAFi1ItLv+ z8&@$~F4$AsNlbB_Y8x*icrGON6yN?rixXlvDm0I+uhr9qaygg9+?pQb%agZB3D#$#n%Xr@q|P>lHRdf(8|qD_bv9 z2ReIGE?9g&z@i;aTUh8FFZnFJuC6Y&*q)d}TLG~U?bs`M-mc@%9(Y2H5%Hq<3K8P` zuP++&UC8u+eD?iYeLf!a{C*;c<=H)oh`&|C+on19x%%U3{4CpgY?d80_h~eu7WuJx z+cO0%7pRt@Hi>eJ^GxkI;Gr6g_us7FBbbx0qDMNMa@29Lk#Jt4%RrH(Mf$t&txR;!msfm zoL}ol8E&GHTwgYwQ_*;nyxH;mkCJ={0&#k)`QcA|$*!~gJ=bJfpEK8QWPV!KCl|G) zn$+YHcFHg8ZZBE4U2RhvB|rMrm%@UPctgs<&Iu$EW_u}HYq7*@{2wPzMXPUpA&#PW_R)+0^$;)uLvGY9iTZq*YFM*ni5px}G#61JhNk){3= zZ-^J>fHf^BORpo!8Yx} zgU$p>SK)MfT1Yf|;t>Hx`Ch;D`*$0oM6FLf<9w=MRNv;da8$%p=!l@c#K=B)TsvVo zDy7Ktt$Cw4k8f6ayA=eG`8rRgOp~VAeB=gi;B6l7KVk`WI5j(`FYSoTK<(O4z#M&u z99Ahr=hEHhhj|#=*1Kemj1_|w9ZlEmA%48C zi_Tc?#fP-xRqcM0VG?1-VtyEJZ^ea%+pu4My2!kK{kr+*`%8Xr-Bs(g6ey#vq>lPC`dLwB0rg?L z@lUz@yEld(GB=X(zKhSj8lSW9Z}y8onGkw@n1yA;P>FX+?3~@m_g9+PMn!RPvbaql z`Vq`A3dwPGrpKB-!l5VM>+Pc{VlKX{+H1)gU))#hh|9NWnO&agX)kabs`hL4TN{`d z{Snd7uaLrfyC3d%gEb&`sVn=I^G3DMt>VUX{p=VPPR`lOSFXtBR&QpA+cdM|*Hl9; z=5AG-mam2!i6*LtD413ITBk<=2smFG%%Q&Oh1A6d8qJpDWk?j)_+gGk$O&zG9LJ+aW)+FT= z-E?jJ>fd{62p0tdX6cjh#{kYgFjG=`aAvraQ0sUZj_G$YV+?@OfQUsN40`I^)3d+` zz)$pl6!<;hLz<&RFC``>p1yFw7)mPttzrk&kCZT8^*TO90vbG8L+IyCx zb+5H6J@3^=2u4ac4;nME%M7<)<C~Up!@s`;r3CEmh@qA4 zqs7y&U%#qATN#De^6zu*FTEk>KJzD3-EinQpsI5&fAjm-#?%=mSsILk7yOBCxmzK)lEli>AzPgP!CUTm+u;&OF}K2ns)h9D=!={=9#{q^}VFKoQa;;1T~ z>|F)D&VMecPseV~pCY)W+j}L;q~<9T$D&Wg6D5x62|ozfItREw5lKH&|14zCE1OtN z>egvSHi>y3u7TMB(`?hK-u7z9UcO1Ml2*I;W?D@ryd5l^_4e)8r>?XON5eVxq`jM@ z?sDn`5T#D;ElP13C>OK(@rQUr3AD@cRcQ07P(w6`StO{T5>EXFe4258e*E|`-?sHs zPqAZcV~k`&f6nyARO6ti#dj5`j@M4Ii{5z@&6?Hl?a%lFiTCp$oFE4=xGFP(|Ke8` zEtUJ!d#Ts$VC4-CB5sC}?P*%Ek7M1!o(NB%4Xx#LFpqL@)Q`B#+h3mK-~FY;it#Ny za6+o_Q6;boH9(~DjMfy$R+x zuLcO+a-uoDt=RDvuBXbUB(3QYJ&R>;Ayy1Geyyj{t6;P~ymn>@sDL!t894xP$Auufnc>2+22gpPI*D^2!QM@r1iR^f*VN}+moMifK5V0;AW1H2w^+FBg;_d z_wU~qIthT|>HuG+Xv9isXB$aID+E-tpLf6ZR@j78kPGcv7qh#K^P;Q}#Yf9PE_g8&u+u}jkXENoIH_rLF`lyiya?cbQR=8(RF)=YPgwOO8sBbKeHMwWgx&t(?4o@$zM}3g>zEtfAV@yyJ%s{lfiK67$s${^T_;B&Bwi zl)FvGuvS9<5i5A02ZBJdWqnvGgZ|1?yE2=!lMoBP=9}(r1H(ew*A9Kf8YrrCBCc$2 zu95{UzQ248Brr-*=FZoB1sy@c$CA_{g~_-BpcP1(HGmM2q89OE_$E9=O+M0H?dK!w zw^?XdYRHeBOl$-gxp?ijjLYb=Xx16K?!5P?h9I;g=FokPWzp3u657FL7CDVOUpa)+ zWwu`y+wtLbPns}0iXsXQDyMzObJ zHr70>56%NbhwU&xkqFWNf(wEOT|!`o@(xfzX{!=JAE2R3jXK!gc5G?Btr|YJNVYxUs&H|!aE*rSV&MNDY{D+9CvCG&GtMb&9sLCRz z0V4=?$JO`b1m8tfS@#)(j1X41Y6D#j6vnjjY}&8z<&+Ha-|=NCgBO5xAQC)hjS53` z(lj?53HL^>PJw#jI^CrKsei_?uUIm2pu)qa_gd7si>Vs1e1QMofMBJbaKWn)lWSb| z8{1xp)nTy>*j`ZHnE%2&2Fg}gEvJuAXUieDw)p;gg}Ux1Kq=sb1dO=)ip@*FvscnM zMeWFvAD{{CDv|8<&U77RwBUwcbB(*5Yg%Pl7s@SN1W2;VxZG9st%%udk3PZQFWt1N zoZffyjoO<(xl!HhJ`G(=UkhGAWn$U?%<=dWVYat5b4g! zj&gT$jKfg%fNQHalYm5r@lYW)d}wRZeQs4P)5pn2{s~l}^}<$ZqleZmTNgm05i-bA zWRr4;a#(LS|IpI21v_s3P3)XgU7H=gZMGfAoPnv=*pc?fnHSF$HOI;FNIUhP>y&l& z_5&gYi1Ammna{xN;H;B+&Z}vVU!)_HO@3U>d^V`}EgA4Ff0A;D<_eAm*_K~^)cqsj#i73_V=&{fT3D*3bfe zK*1DnMxY+zHiLIx8N{{7TYt~d9KF7^HWvw;CB<6-Vpr?)+&-FOL!5_7}u#%|WKY98XhtCK5<0Kbp)yIz?M?)J5j5wl5mTF3nukfA!M7KB| z>#PPCw8`Csy&S}nfb=kfe};H>l%m=I`gZ~_kSDu|+A-dTb|H{na6h~+ZL4(FKmpE} zMJg5yEcZ_!EVzJ_veU5DrgjIQ0IC;jmpGY3i&?#h9_xe7j;nBQu&e11^~})1W7?%yd;otellzFicB=mr@D}(+Jo%J;t0YD(wb`Hxn$Rm@ zlW=Vnv?5C(<*wP7s?tvJn?-h=+EvUZU1Q5r?bg4nJpX*8?lq6Kb)LEn ziIn#002jvi3F8TCr9se~Q|vl35~Lo|F-!f`GsZ_q5|dLHw^o}7&?h%0lR_GMANFci zyeM)>-%5@JOk`S0J5f!J(MUZ%v?Tq%Jn`rm$Z# z=cwt+MKCKJI@Z6xf?{qV;KsHIwJh{8&2Wptu0z+ZAkgw&PRK*#0q*Es5KKq=oCz9z zNl;uB=k<{x5TwOv?*enU!kz#4G_^bM*}g?)hCLliMb6P zLw@|ul?zWK9J;TAiy#8XMm@xW7Os2s6;&KSx{d82Aix{=-3=~Ps2D)IR1FNGjH`Wn z3e#^SJ-I+xZin+8gOa>^z7~!PG>_VYR!!6+DH-%`(A{R@!Z?&e+6W6l<&idnXw8Kl4UfF*wG^%oJDq4`P_Q?UmWX4(VCL*LfIf&`lH3#4qWpm?p}HIMRXkJaP)Z6FZe9<8}%?CQG3jZ?jwf} zpMusYcKh=lETC*7j=zE;g~8yVDMS&PO)emUvtIDbgQIJ85yH;qbfLT&oMcZmwLy`c zUJoIFAYs>`0tZ`^KZ5k+KkqU725mnBf3;2-fj|Mkz>A+5M3M(u;kc8fG{fLfj8@E5 zheOSZUI-X8DsiHCgrM>B=?}y$n*fZq9=N_!xd#8Ee&FtNoG0LK53mQ!(Xd>o<&wrD ziJb~_48OqDJIO#>1NA};EPxc-)Q-0T0_xUpwLmojY0t3 zsUej6(rJn1VxKT{-#FPo|*K=(?RhZC5qBQyPF1wy8kGL9h-0?4Pny;=4Y4d|wr ztr>Ap*LpR=75Ow`-u&bBh5~l00H1vECHSm4;rlB2AHiWKgZ9*bDU(8ejs9}!oF4>9 zjaWcBHJWJA(#{E&FJF$D(^+g)=AsN(ph@+P#(ZacLw;my*#{1Rl$7z9HTIfHiTq2s zK|eD%M|)j?G6HB|3MT=-@2f^8Wi&V0>EZtmJdU25-i8M$D>c|sJ{7f4 z5(CW{0_B)O4Qt?gNB7n+amcBlqQs6Z{C?2a0%6^S2M;Y1WjOxVJ(e-Z0H^Z{h9i+r z6$cK$4>1wylW?BDTdnjlhzyAS&tJRFuufo3ML+Hd} z@}2$-5v_~@2&ygnmh>4Q9A#C;WiF_2R_0DNDwkJA@AlvyRCgDCe`^C%g{9`-qnI&B z32s6Y+gF@jHZxe&*KYm!5xtmWuSB#gF4bYM(h&R;i&d|&A4hOo)qY!zkI>9l2h_&{ zK_=z+l<~yd+bs}|Cl4yIpN-fXrN2$;NawkC@7{PhZ9!d~%2a1&RX6Utu(SJ-&5${GP3^!#ifGeKP(`y=SZZnfQ{I=H?nt4ns$F z8L0NFm`Zee3>9fQR?2blZ!#0st;Laxi%W!GGj1N6d_ua}mr2Z= zAw$GHX+NW-2~{pa`_L?aqGwd>a0cpksq5UJe2@F3r%aN1N`HeT@|tK;9YWA53hH<` ztm~!&aT;Q83GOyQ+h@(*Zq;k1TNDaXI$Fzt^+o*t_ymB*M{4vo0~`5CS6x(qJ;ai!X;c~BY}Q+@!bP^;A@&(+CUMk$eUkZp2oUuNsOK~Dc8-= z&n98TJ?G6?PWdcPa>K2M1FnwtBYkdl`gWb)e-c%~F|{mv1V`ZXmSi_XT(FDkvOfNP zy^wjuJ*HDX4d~8M&P_sP{ZFR(zmI&gNKoDV{*o1jGI)Re`lX#~Di2|<{(>ASWOyn< zKu?UEl$2yO_9FsQaz2;`bksLcnALNP%aVcGxD3}&;iW8UgChW0s?GM7jRl{dRf(L~ z3l-Pl!6`)8w9;U6ZB8WV>Z>rYYgMjYds%4PsstT#tb_vGAq5UaOJq+OF0Ih9uglnd zZjg)>hxQ%~vjL59vN}@EgF;@5qoP}tE@Lm!nm}JdJ8C4M_4uF)uFRlrSX-`{g3d*_NZyqins@rYik*j?%56Sw=9{Lso*xy^27~XaHS`Q2-77KYZ$iK?II$1 zmONWL+S?xts#HvnAUF4~;x$+|bH}~s#L>}=^wZ^$;4&+69fqzs&JYMV?kzN+`ucEw z(BD?>e0#A`72>3oUbVH7J0mXngo#67q6NQZ41+P=eJIHTiU2%J2arCmeOK0kgZi~= z5=3+;;BUg8QI&U5njh@7#?K|Eq=6#26EbCfsaFAFLeE||h6fOf4Gf{uE#GtPpYR&- zhid>jt@ZPukA%MwGHi4GlmVDl1zBG8u7BF|`4G=l71Y#p5zT>Ms_QARX}ZbrwA7v`T!2nxgh@LO238)0tLyKnfu_^| z)w}6)lbmW2$PRxK^O+cJ<;1&Ff}}yV5EDv(9xN_tPcr zVO_?Axsu%OPV)h3%zw)K|3}8s{)2c-4d2I6`xofPS&_Xv9HbgM4FhFE%ySEh-Pphm>jq-mvJetuZYyar*Fx%;k+0y>g*q3sP6 zYP(MUytq0$AgGt2`x-O@H89^aVxecN0`D3{M@rTR5g^Og;}r-!CHjZjjXFY)V5_w( zIiG&7rQJLzLwB}T_F0zqKL9kJcg(|ocMVwOTtC>{0$+40f8g(GQ}%K1b`q*2G_C#J z94%mutZrqKSgB@1Rx;hU%71BLef%7|o%p5He^oNy^!{;1`|AWOY=Js>r}BXl3hna@ed zY5sH!cMYqDJ<@omK6~%W=g*DbV%>RoD7Nc7$f-~nxZ8X#M zGT%aZ{GOJd`cEoyTXdz(M#|oV0cWYQD_L-UrgV5H_~Zd^|ulh^SXG9Uov&<(Urf?BBt>Pu7OFNNK0ot>fG ztpMu>&zg8|1AkiCdj;vRG6AIMc+{zj{&=rOrh0y9xC|)FcdRGML1_H*@4&DDhAOYs zwX@bh46YQSq@4bBc@AEG6UJ&RSJN4kD~<{om@An9b8p<)T?h$?Dxx{$+1r^jt;9!m zp_8Gz&EU^;sv-9;jV0)>S&fqxhY%ot|I(DOi~|2Dir2e8$p66$;crV&>%DqChh|M( zX-n)OP*7C_^;v@M5`n-LTsSB;Q5Fh}T6|>pJ{@N$IBrozGA1`}+>nlE1{-Fve+Mvz zI!G8z4{|`32?z4uCqy;Y1|vTT{*}5Jx~izUjUnA2ZuoAIOXMA4CiFKBPf!mrjLcWM z+u9O9Wajvp@+GXi(`y;*V{gpmlV?%k#y4NL%w??c#~-NJZ`eB%u7y!8SyI`Zo3vw9 zyndF3#>aLz_Pj^VhH2z>Yl<(Y|N8BMju3TcqWfUbV?jo`%}a)nq*kuC;95%lx(8cC z<;ka#iMx0y#$PQ&xINk4*B|uFe3(~V>ggxJc!k)sTlsQiY1`vLH~RFC7PV^AUD-_{ zrCk2&pM>_$wYALp!{pUzVd3U1@eGE~(=qP}-~pJ)>6-{$3~86=v&76*E(I@>IvafD z-Djd~s)!wN%GfI{+ylEH4B2C%%r}>(K*BkK&Iwuq1{j|v&$ReNOPuU-hSdB_8}ZIlv!Mb68L(nk>AAq(M|_h7qmbx;e%@;GV?F5?uQ#y_UlK8b_Ox zMpsL$8jhwxh-$u9dK~xc*}e%Fki`MsobACUC=YJ6P3OR*f}EsX$KB7LKgXdpVpk+! zXCrN5734~}wSHVknF*Ipi^m+k9ZX@josE`&O?JZ01>&x_neW1tj3FM&3p);qHO7^m zH=qITX#I#3aV~%8d_`e!F4npFHO!OR)Gct23sv*^c@CY#TdjiNUf8$`ywFe^yl9Y!f$ zW8h#HriYbp={7`)NDn+W`lPft7_jRneqfleGfhB+-g8y-vxiGdT9P@@H~7HxF<)wN6M zErNwMFRL#vvht9%Q2Bd`0&%6iVtH5mfdQh#FO<@GloF&T$nV|Sp6j{r8xxRDlFAM& zbzJ_vlahWUY;y(V4s=3r>sF=bM;s&$f)Px%f~x-bsvx79;1MWE%d%Xln(=l{?WAb^ z#{L&i2It~E2JgWm{O%zF{b{ULx^yWX9W;)%Nf6bmO71+GP3?sf3^d{>i$DujG{&!d zqMVRn1w#sP0EpKe+F!imB!ZiGJ40yG^Hzz|O20qww}K`gGk^^D&;jt471*^$8J+?l zWc)-ej1AMwTd40cch9aR3D9)3-C{O6oUHpROrP0<+&k9p1Z6Vy&IdiQ>{CE)h^Ub6 z+q;e~ubK9yxF{(7&4gWwbBk0!l^|p)4z-%_j&oIO2^(cu7|OB^zt8GSGt7I-m+)sJ zq>Wqe6seX9y7j=r|3h$YACoTf`hAR)qX0V27EDNUlr2|&%tiJjDM#Q8kCMhE^#^8l6%5oLnc@KIS4@@tIg7p#e zF$ef_4Y_JH!ShB(yh!P3*RE&?x*HX#FmgywY6u3>&@oBid2_-;mqm-@ zCgq=W;?WUMUH!r;pH`e%OW*ps=LYnPruEM-&gDIu6tO_PcmMi~a!4P?rOXjNh z44ogj@Knwb^~h@BLD$1U80YMa48W~XyEwU4hEK1MX9rRg99E9SnnFTbl#4El$GW-a zst0sKcqXnbq~bTW`*AJNPP7~!vLgZ1ENNOFV*Bvp?b+!v)6%=Q@k@+Z&f;R%Hr^GqRPDaIH#OtoH z)er3~GvAg^b5(h$$=5pPW}so$tRDa8n4c+$tJ~YuE>Hce-@x+3Jj~bJ*->oG83N}! zd2?sqfz(3ocm1pmU?0~y4wwmEP`h+mOo8K43Ly}3*W=b2F-6?9Qd@1}QXB57pnzQ0 z8OiFMO?#alPk22*o}ehX&vU6nA$tbM*BuF43%LIog`j6qAd?M8>+h`%fHRAqj!R{ZiM{cy-q>Na7eRfEiQQ;?rGNG{h8dd8- zdPq07i%=#;x2tj)oo)S*SkduphwG;WcCfMx^q4QYY0HsC?w2+*S0^bel5Io{K?Rir z&9GLyI5>pjCP{;?O2PVcE9{k>UdeOm3FnQt+wtG8VUyl%v(}TD5m@r)PJH&}c9B@dL=OyoH6|Y>^WEHOR+lHN^t?%w2?Ur)I-SKWGHtj#OH)uk@G znJ4~WqT_7o2ov7Q9K0e+f6l3mpD-k60}c;n;#Gx%y0+XxB8;SK0#R#9E`b+N8qn8+ zaPV}0w4kuZqLxWK`tq9Id!@bNth`gZ@nFZx0#}ya9jc7HZ&+j(5q&zTy!yu82fkwTZLyl(ZSN2DHf6onx@rD%*1`mL9V=bh?At}J z-K&v9btsy~0--v&Z&E~?gD$ne*X?C=@Gav!^ZWDxYLUx74GD6z8a84qJE4bM`|-iI zJD0R-xk(0k6rQgs_qA$;Nu@@V-62@C(%m7gFhiG#Nk}`S5(2`|4TGY9q?9zMFm%_z zP~SE0`|h>Z-fMsRSl_>I@Abn&4~8e^dG7nX&hq(fwVO1D&K^P#ghuh!H4OwI4@3~M zp9d-7C&KN6WAK**>bf3E)5QkmVeV>;sG6hhIl7=6?JQW_tzF&hT$}}Y`1yFmxmfO^ zQ1{#ANAw5p&WXvJbN9b_gO;t9b3ImS^1D5LH|Y zdT(zp8eJ!0F*{KDg1V)*NAZ`ydCg-vPv3mQ%VUI3=11kD3c8bc2IefseOtGDTPNbYFe`C{^>5>@Wk%$1 zCk|Ew0u}ttVtEuHLl7dSn6^>=Ab3jxvirf47O`rVx^eAz^TAi*y7 z!u1yovU{8AeI+iYZ?_{>2TNQ=oLd#@>88^rW?jot>^jpl3h&+#cb|=^@U^gbckSA> zh9Qp;?uoWUQDYo$)#{rvto)Nh^Oh+nfBLlqv5DUfD_j+B6KZxN)m0~WjPxEpdX%ab zFOh%m*JmNG05U5k@f|;ML?FmE!K?0Z)M*pXtC>34R|0l7(?6P28Mx2(o-eZRc8!)q zPe({?FV=P>D;zkkm8KGFQJpC5Q%n`=9mk&FDL?Ez!+rc3EtAyc{fAEQg&5=ftEMcv zm#rHk_&;4|#-5*^NDL79HKnE!Cw9kseU@W$Wg=SF)USJ%)1d&|>AI z14l$2=$K+VdR#~R2#Ql3sn#`tWaGj+6S8|@B2N7cwuydhA=2x;j8!;9;OJ>2MgG>U zXEU1^!mv+^7jb#mXCZdfe|xEZ+{kUL;n#F$ zdWddp$*^}*L|=smmQz?@TDokwH_y`La_2{VLEUTD9?x{7D$o1%skp$dG6>~863&mk5!7KHp(WS(VxW3kgfieq2C z2G&Gckf~MhgIFO(qe4si$>YZ{^~SiiDJHa0rRSZew2bzR(_#v97TxpO#;*A0SV8*b zaN=U^5&Y*T)NFK9j^=HMdxgNV^yUs>gq4l04z(7{EIV8k(3T)=^Kmrvyh8J+wCLQl zPQoZNnU&KkvVEaf`dv|bJA@`!LOiB^YmxL=5u4^Sf+q9HK}@dEO<@;<+UnU$OckCh z7=E;Ajxn$MD(u+%ecuHdwSI5+x&ETVn~GDOxTa`c%t8QhirtOLEDs7_ zp6;!V6W`l2T~h0VU5Gg)Yg%Z1G?4LNAfl0N_3e$#jZEi$gOqUDVb4(-EaJ(IdZXy* z`0>>_(blR3cyV^+N7Q}tgNg<--kXl)kIv;ZX7a*I#qbR zxnNDXYNb_jeAA^Ki`%sqQI}W|E|craHWcmDNE@o@Oj8x)zH40Jz9zx$SK0W1^FhkG zbi2-HSc&Rv>IshJlh|}w-gxnk)!{3x%}(y}x`ug?k#IGnTZ?biA9{$|Kq%jw=v@HxM+2}NKHjG%j(NA1TkaM{s?>&?=*;!mCaM;A-0MhDO#71)mgI{f%(H+&jdZ%A|aw6S#w z=3?mmGZcT#1q2DShJ}M5QRfjDs(MfVZD5c-AZJ6S$y3eE&5toKaIMYsL_dD~_(W41 zMpXTZZjNK)%)2z@D6ab>9{9$SUie7eW`E-?C9KZ(GB;as)MRK6|LW#yzFNY-Wc&Nh zIbk^azK4N&2l!-UhW7~>>uJ}He0hFcwPv8mAzD9W)f;Xz>;RLe+>O^~t#!U73fcyj zUEWDk#lT=1G|E@P>Lqaw&1h4@9AJenE)uQQas9S5MUu(5Vw3ox6PH7$_Zy?h^k^c9 zF|Vp+E+a^(G*5IB*FG%GwZ4J_Du#M>uKNSM*QU!70(QL!b15m8bTc$kjo#mQy{x|r zXyS4G=1rfuEK^){*3A&6Z!$OKpEU3kl9{@E#!@afTk7XCiZBOfTb z$fgl=phW2AbPfE_xOjOh_2;qU$E^XK1@8TnBgm+Z-nnyUoET9(x$D0}^qT8zy1=cM z%hET|u5;BXl~e0OXpjHga(9zNB&V)gg6D54H}T6=h_C(KrbG$P#iSEldVJa;Kx}r= z>$CbChQ%MZ+y-y>9|`n6>NfY58)i_Ce^nJs$;1$G*Rf}9%jF}dt8SzPQ_c3~z1O`x z)14LFm8q?K=roUGd8F`TCK+G7x)A1t6|rbuQxS{;CM1$69A38E_?wpBzYO8LQ>yHb zrFJ()({KTe5gaB2ui^_jSvsn&6Rq6R-m83;)Q=uMbQX)6?|J!w0zs^7{Fyh;oIQI5 zE*=US!F#S}fK@4Car4Yj;@f4ljR9N$j?1ukHZ2<^L5831&dPTgkx3*tP$#>yN@Co1 zU^!Tcz*YOFbt>GQ^z$s9^voJm4dt3EB!;x_B;X?{rP@fRsq!h|GOadxae~9O(~Sg5 z^Z}C&<9QuASJNw^olRje>+ipII@kScU0t0LZ08h{DsLP5n3ah(hC&C-aQ~emZF)i=GtTIvAHw^n z|5-zZMSEYkc(J8F0H%D@)kl?4TfPJzI(a1q@!XlUfX(-jjpfwC*5=Lz1$p^qPdOVh3M5oK4!<(6XGtQpp?Uw; zUHgw8#0HigJLpS&E;pv6&SLAv&8@8y2%>)iQ1!pw_y2eXV37YUQv9nQK^23Dc`aK} zQB#Ni{Arc}D01o2r4&8?;Yu%!GFNL&nF|->qqy{cS1-U@UvA{Y#_Vh^+}?9Z+-q+O6@D(azYTAadyTT|-`lghT_Be9JYjwSq~i zQRR)cYk&8ILDIe1c2n5l$F2HcMpd(cO0R-KyUsdCprL{U)6#pN7;GCt&yUY+Z?0Ga zXX_|OJ77oeZVXiWRlp8a0nT*^I9WDoVOUzhL=fIH&dROKh-XRAX(`LFBpk82zmZI3^p0&`}p1ONe{YU1Ei_NPp^poz?$-|O?QXyY!Q^pT=7VLow3FhKPD_uI9sn&?x)`(b%_eCQQVlR zX>@S%h~zKzDgL#xo@~QtO6*~HdVKg*0#%&Yefc+U-lTXg{WjTJTZd=sCo|p z9=A@iCZva4q-e$5?%3ZV=MbDv$4? z1{2j*7iTAW@_W<;%DmSyW9j1tS7;f;zN1(r2Vj=CY_0)1KAR)s17fD}7drYRs*$%3 zz+hhmgk8`#v8!3(T6l~D(%iI+Z%Vxtc0rV3bakrJW;=Nll%7aicDRNw)uBTI=R4d0 zt{WV;Whie?9plT(%d?Sx+S(N_cK=;0ejru(v>Ptxmu@-Au5*8uH({QeBmnP^)+wS; zh0XDjx+0FfEU?zxf^h3|0~l5_R9bE@KdNqR>WnN+JaGE3C~iZg66c}jW^=!0YTil~ zMhVod?!#uVjGC{okSFoXxNdLUr5=vKq^lZv7T5K2%^o}s3c_sdZHyk#FTIz%X%d3B!#73(cRIeZGuv07ItfG}R@}eB zeQs>7WTdr5mlhQ6ltf_c$rKM>wmgWmtU6F_#KLkvNDsQ@oU z>nx0pa_oF3|7YmZzd8v17YypoD1<2-J=+g^5EAHrVI3$lHknE#QDVUY3dt$NKUDbixfj39C>RL>AG z|839XU&>Ka7XY^+Gc?nc0Mo6(Z;@$Ie=DSDQ0&M-axk|2Bd(V?{KFHc2+C-pX`&yebR|IXWsH)+MaS3dw#&9+?j zZQm);kSRi~=k*uZhO{APPMok3fipSOAts>1Zv->gk(*@cWRJb%tquiy1%sp~E7~sU ze7OB-kWhO>Hf1`d%xxm7zsRBK#YxUe^0Z;+-rVH2`bqz`WqY88-=fsHZ8Od|QGpNG zkm;PUKNsdJn^o1`?$+|pM@N)Ez&c-k&;1arvoKmliH0m}wY?2t_?3Is-*HEQ`@gRh zyn5MsNGD#>BMRmfhTk*OV_~?X^u8s%sB_%4n>U*V%iOGMpVA08^$QXB)f1#R4$v}$ zDg@ES4oDQMix>D6VRYLxYcq3Bey~Kg*5^2nNG%3xW$Dc644)z~yK@MRIL%owtE7a| zcoG~rGlIiWnPnV3e*9v62(xHY@#MAOv%>ZbaP6Vv(yL+;g1txS>Fqub{3o1@{WhZq z2q`Idg(gMGkR#{_v$FvctdDtGOn+uyj$)&bpd10kjRHA<*9ob;S9hi$U!5wHzTbb~PGC6SDa8d_rGKc06bt>yj)rRRE zsWSm9B~Xqk(EB8bNub>4wAnXOqtuZ>?p z_!iL-p`PV#7C_KIt{>zd!Y{`)rA=AA7;nK~7N}42G_!WVf%z8HW}wAXbQk;xL+n&k zRL7W^UDo%On|L+AxGR*p3BR*Xp4!aFC~geqvdqq^W~_kq3;lHUso&qZfyFer!CRMO zDMX?`O#!7r)$URqXB(Un2eQmUN0BrN29`}{Q;FAa`?GK^eJ;d#u(aK%+6f5C?~knU zB(Jr~UsXv-sc8ky6o{kviQ8S{b#wZ8M0WeTQNF4IAOezq57~yP(@YE?47#cJCjc`; z$lXreE3j!9t7i^S87P3u$^9%w{1-nRusQq_aE4^8y5bx0=m1dgoxiu?2)fH;_rT;? z20ICZ-pi@K1eSFmSMUD80$cT|u8eRJ3~`Z-mhn@Zft#nMtdZT@)+DaaIoKdD%4;4X zy~R$3`oJsu6954ba*xAFIb<+Y<@Bq(i@;qSujYnVuG076av6W9v=_t%#hS1O!`B=jpim*x^qlP+)`+1*}g z)5tLr_nI%8XeR#-3)N%9V{KZyuN?h{AAO9To`avCUn5EG!K#=o9AnjWrbfTOCU$18 z?(5fY$JM3I+qNY{GKjn00{0oP>{CRBenHyy*2rE|dZNDpSiB2kgK!+(!Em`uT`bQ6 zZm+r$Y@xKTE_=H>X@+7#LR$7+AIz)aj#;Q%$y*boE2w>=Yk-jsL>Oh?S%b z^jV(`0~4jAO0KX8uk>;Sj+aZ?|HmT*GMNGp(UGamDek+G+c#3=pzF2t^>pVl1Du!c zAX%=6|D3p+eTiw?v5lAxkrik2{X|H;A0hHc;O@<>w zzZxJgy%xHcCrG5eOo#z~J6{i}1SD!HdcX)+6%Nzr8!X9G7)N&KKi*RWj~R`3%Uj`a zAbGcd?pAIuH|6_nxp$Ipz(Pr%(}z2++2aoU_3IaG3*0T(E@R*(3mboS8Yqrz!H712 zsUGicOWJ5PcgeKQpFh7morO&s>VvDZC>k)*4ym3i2{(O8t7mzc@j{RmDNgpBI;>!ay^nx}G4%jL-&_JT3pVd4> zBWu&lp!Adz{{)Era?wqZGJo#As$&Odw&C&V6(3efRf=>zOAaH&t73wLLUPoB611WE zHE>_2_SeABx&_jtW7m;%pPrUTr^EKh*tc+2E-n>PMgrRKO=xJS#irNFxFV2d)y-$m z4(jJyeWRhiBkD3tGEppAy_%x9K`Ov>S%y805g=g$P=asm?{9W)7r}k(dlMM5pMjZ~ zm-KSYYoC4t6D|duvyOMysYw}5+cL?j3ucs5g86R^{&7c|>bU|~gDuA6@K8EF+_?x3 zKqbFz0P>*Ck-c5p&tsX|Sz{n_MXNqTVkc)4-EtC{DZl&Dbf_H7%EQw-SnWRdHiFww zhm^BWP#!K#_Ek}NMdb;ejO&8K-|0(j_mOHpjb!=#$4{*J*Mn?Kx!e9uz_yiv6czQA zfExy;-iwbQ4H*_}03saf<`6n8FaU>JJWrdftE1T;UxC3LqeYo>pB|NXdPE?rm zEJg{)Up88)`Fi#+&z*OVfzs?6awHD&7jA8Wq|1A7f@nMmS~w}C&} zi#oy-u?BZ(-cN$+nu{nz2!AT#B4BA&O`ohr zHy12zosmmbuKDusz8Q)82sV{~aW=jS>R9g5D#V-5ZRLae1_6r~Vjhfv>J3 zu$TUKB*XvnzK2T9z8c7mNfGwG0|%4=R9Im$eO(6N2nW!`EFV336d>vtcFUM28kUEa zQWO`5*Ya4{hYy$g!2Q*w_p65G?BxU|vs!cyylcA4}AjRS-mi56MFwgt`&JA;mrk8a8Sh=%4Ab_6MA{o46P0?)(D}aS2_1<$u zJ!9a*&SwKrncDRRB>b}ehy0rYWe7w6I)u#j@`nGE{4?BRQ;WMyTG2O|{j>o6*nlCv z0g3SK;eP7MVC^J1G7i}2KAlN`3!#yle-neMr?W2tV;NrumE#AFkx!IvfB8hj2n4af zoxWtLKny%(P`w+|k2g%Yb(F-RD%XA(x`O2<cmxPW>k&D{mqqE`5D@n$V9e z0UK#*h5zilZ)DF->rQL+SmOt9$z!8xNjudU|A6AaTW2$;uvI)bNj7eA(mv}^ro4bx zy-diCVm+foAw~>N(eeJ~5qe?!h<*F_58XB)gq-2O@Tlz=Y2u;Dg8qZuRUG_n)O9=+}g4;Q<^(mV)5)!w~-Z#_R)mTz8587t!Sw#>9=Wd@?} zR}A7BkZ9i}{{_Kr3g~*9vNNgSSB2;xmhWob_hZ0sYdsR=&^Kd`A$+~ZALAW4^Oty| z7M-~S0pXwGEnEyh*UVs?AuKG6G@}Zdo06_RQfcVK8-K2?9kMp@-?Tn{p~7Q+PUi>S8YN}sjXDrSqZc<` ze{DR^1yzVse;{#Je3~WSo>2>9k3z7Kshu)+aeWkG2gPDHp8GO^P?%wWV$QEvXRTU zT+5)yo)x#ZTj90c0-&TL4@p>F(MkU7C;#2bzY3<9++S${B`w3`aJ00Kd#5${#RjBg zvEO1+(U1V3VOITbpvq92EPI#cEm4mqkf>eZ7r&TZSSeFdd`4!y*HTn!0m?Eh^;%`4cg z_^o(9jz|tD%Jl4jsD0;f+qo2;rHg$l z7d^N_xqp9bOSt>Ri#vBWE+U^3KRj9#oi-yL%qzt6n%R(nelfJ~$>HYI`M7AW-}!f8 z#FUCj`X4a;3}8AyajI7){guD8AF&X^+6}1dw7AXaU4?=2u@MNcQiZmqrDeoL6imt1@4@Vv~EYFbex8~h{mqO5wPo*b{ zD6fiPOs-y!{!Q{yDqcJahzfL_v}S`!12?FcC@HVy z7-i{ci9}}K#Rq@%{wkT=(pTfq9Gv@3Rqa-opyZ-yD~4O9+kpuUoj!UC3_2q|-KcV? z9JVx`0DD(h8M8yc?WvY{r|fy4iVm|RMLl>REwtDY!(Z!*Roo#@S9j#M5Mt5^svQ2#E zgbPg46=c6e@~q}4->O=wJ4cEvSI7t}ZL*l_zZkve=1F~_-@ku1OS~^0h%k5@uNi4;n1!U?Ux zaT%YPV`y`W6=LZ|(Vtj7aTU}O+N!b*qFkEvur+JR)~LQbLKn*cPV;^`6Qgx_A1D_+h%gn4wf zQCWvC4*0Hawn32*B%}gcu$o5ghsukx^ztlnd^eZN;MP;kLA`LTO33 z4ZqnN8cNp_u(xHZ8+k?B2+AIOYs!R{IB~(g36&fsR*%ZZkN4?iPCskEu+eV3~Ks;L@s?w=j3MCQ(v@H#ljnOMDk{v=&{b z`#qN8CAh75NzH{qys@ElQIYR>b8MP$mz{Z6mMtC}%tAf;c;B^d-KtjYnN*XQt3Fl? zL?2`9aOz&-(VA^K33Co7r*@oP5&oKJwm-x`t3+jOf*tiU%^RWf7|e;^Ek9o(WDCD{6MtEWu1 z9vP>jT`T3DOYiKg+b0a6s=Kud(Kp5~p*|B>gF|x+|NKCR)bhdX-x+lD^iyvk-QF}Y zERk1GXlWBGTDuD}F->^W20{m0gI_r=(u2?>(k&x-;-?gxSUYvbWc-Mwq`*VFPFGrK z-m|B3<8G68EVqrq#aro=X;e5TDU2 zXC8l4n0f=to|An^sb1HvDY*E>_KfMC0JnX=MSAwBjO0;DX*+5&65A^Jf}CcT)} zJJ@^;4?%?xH>bgHU*Wt%6+A^O_(M&vct1jPxy9cv;16HIi}7aL0KG!iC>w6$H}y(+ zsFa_}nujW6B`r>ViADR>>&erKnOJS1#4@h&uD?Gu{APmpG>2et+sb{3r}~B=($b4H z6wAOaW244M#et|wsC<}0Lb`_8z3j)RKJ&2H!l5Zq&5A=WHz^}4BO|)MaZ;Z*{>ME( z8sf0s%hF;!iB-R)lyJROv_n=VO#bjq+4SR8biD@y8*USubYy!`q;kqs-FF9(nOT#d z-TeEFlW8GKDLuaakWr|JKBDdV;RKs|`%=L{8m*I8wC;`2($K7B9ywbnHKn3NbHgBd zwf1I3e&NgR11^GE=ViKv_3Q0HR0;EfUA71d6^vdYXX{q=&WSolfF*+2lA3wcDnr=HmOS^d%JL^1V%@Oztr|4 z4yQ#w;qbfOjQNeDY|dM&^;89(t!c?Rjb$OQaww z>UBMnPxB60=GB^T?5JH+0_7o^-0I0*AsP;eoLo|mHnI4Yv8;4Jti znXLml#Dvzj!pbCj5cCD1Tz#Y-itk^V8vy2(*95z3(z#2$9c@qL1PfOh8}P3Ag2b>& zSkRflS1eRJ)a_(|P0|P{w)4Tb>gm+t%N`g|^X3%&Rqin96|JmN?DT)=$QlP^Ln>sW z5x8QGr5F@$usa=-b25fu&Z1N2S~iwg-vq{h^BMo7`mOZ=7m|2620^v~Us2@Ep4d)B z=6-{B+%H=-E)oOe+E}k#Q7ZRXa5hL^tu?4P?^m=wIl2bmuWINYDiwd}J zv^jKUY@WSxA7RrC(a4Nu_FE0}$+*ZUl8l}pdS@8768El#q}NiD>h04|Lc=%joutX{ z7sxg)C01yg;)-9{OP>QfELQ8?&{(5xRTnWEJ+MhZMb}cZ|9h6`Jmne51K@`EN-97QbtQ{{Cs31ek{KEowZc-#dbxtBhss( zUUAj)psix?Hl!vRnwI1x5oY%Gr_92CjZjxbx74ttB9Gs1umn~@8~vZLd>2^LQ@T>*DjSe?LA4#4P9BfZQHmqfBG|iE~MHt zTh!#{L&t6Q7&&7$T@`BSpHw8;qKB9WkU;HTtq8isz0oN(I0b3m4hE1eo{ z_jhFI+IE~jlrXAWwBOjp;dI`F727@@au#G?!^TY-4>&GJbkf^3V0$SDVng0EWxa~G zXrt(0aSW;GY8c8H&Qdj%!p?X$d8qj;5w)LkPUeU3fk^j9GtIW~EU80dXX(Pk`6%fS z@<9L4X)(Tde3oT1YVbFyakZgYo)Xkb?B>5K5gxl3^eSm|7j-!6hb&{z2TSd>5V{~r z8ODkaqxNe8!3|P($^P)#|4={wu}%JuU7-JOTBR1zdk3iH1d~>-NkSBlAd1)3uI0#^ GJ^Vic5w-;Y literal 0 HcmV?d00001 diff --git a/nehoroshevaa/task2/__init__.py b/nehoroshevaa/task2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nehoroshevaa/task2/builders/__init__.py b/nehoroshevaa/task2/builders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nehoroshevaa/task2/builders/maze_builder.py b/nehoroshevaa/task2/builders/maze_builder.py new file mode 100644 index 0000000..42d4710 --- /dev/null +++ b/nehoroshevaa/task2/builders/maze_builder.py @@ -0,0 +1,4 @@ +class MazeBuilder: + + def build_from_file(self, filename): + raise NotImplementedError \ No newline at end of file diff --git a/nehoroshevaa/task2/builders/text_file_maze_builder.py b/nehoroshevaa/task2/builders/text_file_maze_builder.py new file mode 100644 index 0000000..9c0cbe9 --- /dev/null +++ b/nehoroshevaa/task2/builders/text_file_maze_builder.py @@ -0,0 +1,67 @@ +from builders.maze_builder import MazeBuilder +from models.cell import Cell +from models.maze import Maze + + +class TextFileMazeBuilder(MazeBuilder): + + def create_cell(self, symbol, x, y): + + if symbol == "#": + return Cell(x, y, is_wall=True) + + if symbol == "S": + return Cell(x, y, is_start=True) + + if symbol == "E": + return Cell(x, y, is_exit=True) + + if symbol == " ": + return Cell(x, y) + + raise ValueError(f"Unknown symbol: {symbol}") + + def build_from_file(self, filename): + + with open(filename, "r", encoding="utf-8") as file: + rows = [line.rstrip("\n") for line in file] + + if not rows: + raise ValueError("File is empty") + + width = len(rows[0]) + + for row in rows: + if len(row) != width: + raise ValueError("Maze rows must have same length") + + cells = [] + + start_cell = None + exit_cell = None + + for y, row in enumerate(rows): + + current_row = [] + + for x, symbol in enumerate(row): + + cell = self.create_cell(symbol, x, y) + + if cell.is_start: + start_cell = cell + + if cell.is_exit: + exit_cell = cell + + current_row.append(cell) + + cells.append(current_row) + + if start_cell is None: + raise ValueError("Start not found") + + if exit_cell is None: + raise ValueError("Exit not found") + + return Maze(cells, start_cell, exit_cell) \ No newline at end of file diff --git a/nehoroshevaa/task2/experiments/__init__.py b/nehoroshevaa/task2/experiments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nehoroshevaa/task2/experiments/benchmark.py b/nehoroshevaa/task2/experiments/benchmark.py new file mode 100644 index 0000000..ce5c3df --- /dev/null +++ b/nehoroshevaa/task2/experiments/benchmark.py @@ -0,0 +1,60 @@ +from builders.text_file_maze_builder import TextFileMazeBuilder + +from strategies.bfs_strategy import BFS +from strategies.dfs_strategy import DFSStrategy +from strategies.astar_strategy import AStarStrategy + +from solver.maze_solver import MazeSolver + + +mazes = [ + "small.txt", + "medium.txt", + "large.txt", + "no exit.txt", + "empty.txt" +] + +strategies = [ + BFS(), + DFSStrategy(), + AStarStrategy() +] + +results = [] + +builder = TextFileMazeBuilder() + + +for maze_file in mazes: + + maze = builder.build_from_file( + f"mazes/{maze_file}" + ) + + for strategy in strategies: + + solver = MazeSolver( + maze, + strategy + ) + + stats = solver.solve() + + stats.maze_name = maze_file + + results.append(stats) + + +with open( + "experiments/results.csv", + "w" +) as file: + + file.write( + "maze,strategy,time_ms,visited_cells,path_length\n" + ) + + for stat in results: + + file.write(stat.to_csv_row()) \ No newline at end of file diff --git a/nehoroshevaa/task2/experiments/plot_results.py b/nehoroshevaa/task2/experiments/plot_results.py new file mode 100644 index 0000000..4f54deb --- /dev/null +++ b/nehoroshevaa/task2/experiments/plot_results.py @@ -0,0 +1,25 @@ +import pandas as pd +import matplotlib.pyplot as plt + + +data = pd.read_csv("results.csv") + + +for maze_name in data["maze"].unique(): + + maze_data = data[data["maze"] == maze_name] + + plt.figure() + + plt.bar( + maze_data["strategy"], + maze_data["time_ms"] + ) + + plt.title(f"{maze_name} maze") + + plt.ylabel("Time (ms)") + + plt.savefig( + f"{maze_name}.png" + ) \ No newline at end of file diff --git a/nehoroshevaa/task2/main.py b/nehoroshevaa/task2/main.py new file mode 100644 index 0000000..20332c2 --- /dev/null +++ b/nehoroshevaa/task2/main.py @@ -0,0 +1,66 @@ +from builders.text_file_maze_builder import TextFileMazeBuilder + +from strategies.bfs_strategy import BFS +from strategies.dfs_strategy import DFSStrategy +from strategies.astar_strategy import AStarStrategy + +from solver.maze_solver import MazeSolver + +from observer.console_view import ConsoleView + + +mazes = { + "1": "small.txt", + "2": "medium.txt", + "3": "large.txt", + "4": "no_exit.txt" +} + +strategies = { + "1": BFS(), + "2": DFSStrategy(), + "3": AStarStrategy() +} + + +print("Choose maze:") +print("1 - small") +print("2 - medium") +print("3 - large") +print("4 - no_exit") + +maze_choice = input("> ") + +print() + +print("Choose strategy:") +print("1 - BFS") +print("2 - DFS") +print("3 - A*") + +strategy_choice = input("> ") + + +builder = TextFileMazeBuilder() + +maze = builder.build_from_file( + f"mazes/{mazes[maze_choice]}" +) + +strategy = strategies[strategy_choice] + + +solver = MazeSolver( + maze, + strategy +) + +stats = solver.solve() + + +print(stats) + + +view = ConsoleView(maze) + +solver.attach(view) \ No newline at end of file diff --git a/nehoroshevaa/task2/mazes/__init__.py b/nehoroshevaa/task2/mazes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nehoroshevaa/task2/mazes/empty.txt b/nehoroshevaa/task2/mazes/empty.txt new file mode 100644 index 0000000..dfad92f --- /dev/null +++ b/nehoroshevaa/task2/mazes/empty.txt @@ -0,0 +1,10 @@ +S + + + + + + + + + E \ No newline at end of file diff --git a/nehoroshevaa/task2/mazes/large.txt b/nehoroshevaa/task2/mazes/large.txt new file mode 100644 index 0000000..6eba40a --- /dev/null +++ b/nehoroshevaa/task2/mazes/large.txt @@ -0,0 +1,50 @@ +S # # # # + ####### ### # # ########### ##### # ##### ##### # + # # # # # # # # # # +## # # ### ####### ##### ####### # ### ######### # + # # # # # # # # # # # # # + ####### # # # # ### ##### # ### ### # # # # ### # + # # # # # # # # # # # # # # # # # + # ### # ### # ### ### # # ### ### ####### # # ### + # # # # # # # # # # # # # # + # # ############# # ### ### # ######### # # ### # + # # # # # # # # # + ########### ########### # ##### ### ### # # # ### + # # # # # # # # # # # # # # + # # ####### # ### # ##### ### ### ### ### # # # # + # # # # # # # # # # # # # # # # +###### ### ### # # # # # # # ### ### ##### # # # # + # # # # # # # # # # # # # # # + ### # # ######### ### # # # # ####### ##### # # # + # # # # # # # # # # # # # # + ### ### # ##### # # ######### # # # # ##### # # # + # # # # # # # # # # # # +## # ######### # # ### ### # ### ######### ##### # + # # # # # # # # # # # # + ##### # ### # ### ##### # # # ####### ##### # # # + # # # # # # # # # # # # # # # +## # ##### # # ##### ##### ### ### # ### # # # ### + # # # # # # # # # # # # # + ##### # ### # # ##### ### # ### ######### # ##### + # # # # # # # # # # # + # ####### ######### ### ####### # # ####### ### # + # # # # # # # # # # # # # # + # # ####### # # ##### # # ### ### # # # # ##### # + # # # # # # # # # # # # # # # # + # ##### # ####### # # # # # ### # ### # # # ### # + # # # # # # # # # # # # # # # + # ########### # ### ####### ### # ### # # # # # # + # # # # # # # # # # # # # + # # ####### ##### ########### ##### # # ##### # # + # # # # # # # # # # # + ### ### ### # ############### # # # ##### ### ### + # # # # # # # # # # # # # # + # ### ### # ### ##### # # # # # ##### # ### # # # + # # # # # # # # # # # # # # + # # ####### # ### ######### ######### ### # # # # + # # # # # # # # # # # # # # # + ##### # ####### # # # ### # # # # # ### ### # # # + # # # # # # # # # # # # # # # +## ### ##### ####### ### # # ### ##### # ### ### # + # # # # +################################################ E \ No newline at end of file diff --git a/nehoroshevaa/task2/mazes/medium.txt b/nehoroshevaa/task2/mazes/medium.txt new file mode 100644 index 0000000..439365c --- /dev/null +++ b/nehoroshevaa/task2/mazes/medium.txt @@ -0,0 +1,10 @@ +S # + ### ### # + # # # +## # # ### + # # # + ####### # + # # +## # ##### + +######## E \ No newline at end of file diff --git a/nehoroshevaa/task2/mazes/no exit.txt b/nehoroshevaa/task2/mazes/no exit.txt new file mode 100644 index 0000000..cea04f9 --- /dev/null +++ b/nehoroshevaa/task2/mazes/no exit.txt @@ -0,0 +1,10 @@ +S######### +# # +######## # +# # +# ###### # +# # # +###### # # +# # # +# ######## +######## E \ No newline at end of file diff --git a/nehoroshevaa/task2/mazes/small.txt b/nehoroshevaa/task2/mazes/small.txt new file mode 100644 index 0000000..28f1058 --- /dev/null +++ b/nehoroshevaa/task2/mazes/small.txt @@ -0,0 +1,5 @@ +##### +# S # +# ### +# E +##### \ No newline at end of file diff --git a/nehoroshevaa/task2/models/__init__.py b/nehoroshevaa/task2/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nehoroshevaa/task2/models/cell.py b/nehoroshevaa/task2/models/cell.py new file mode 100644 index 0000000..9631a47 --- /dev/null +++ b/nehoroshevaa/task2/models/cell.py @@ -0,0 +1,14 @@ +class Cell: + def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False): + self.x = x + self.y = y + + self.is_wall = is_wall + self.is_start = is_start + self.is_exit = is_exit + + def is_passable(self): + return not self.is_wall + + def __repr__(self): + return f"Cell({self.x}, {self.y})" \ No newline at end of file diff --git a/nehoroshevaa/task2/models/maze.py b/nehoroshevaa/task2/models/maze.py new file mode 100644 index 0000000..ec74f1f --- /dev/null +++ b/nehoroshevaa/task2/models/maze.py @@ -0,0 +1,51 @@ +from models.cell import Cell + + +class Maze: + + def __init__(self, cells, start_cell, exit_cell): + + self.cells = cells + + self.height = len(cells) + self.width = len(cells[0]) + + self.start_cell = start_cell + self.exit_cell = exit_cell + + def get_cell(self, x, y): + + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[y][x] + + return None + + def check_cell(self, x, y): + + cell = self.get_cell(x, y) + + return cell and cell.is_passable() + + def get_neighbors(self, cell: Cell): + + directions = [ + (0, -1), + (0, 1), + (-1, 0), + (1, 0) + ] + + neighbors = [] + + for dx, dy in directions: + + x = cell.x + dx + y = cell.y + dy + + if self.check_cell(x, y): + neighbors.append(self.get_cell(x, y)) + + return neighbors + + def __repr__(self): + return f"Maze({self.width}x{self.height})" \ No newline at end of file diff --git a/nehoroshevaa/task2/models/search_stats.py b/nehoroshevaa/task2/models/search_stats.py new file mode 100644 index 0000000..3f23568 --- /dev/null +++ b/nehoroshevaa/task2/models/search_stats.py @@ -0,0 +1,34 @@ +class SearchStats: + + def __init__(self, + strategy, + maze_name, + duration, + visited_cells, + path_length): + + self.strategy = strategy + self.maze_name = maze_name + self.duration = duration + self.visited_cells = visited_cells + self.path_length = path_length + + def to_csv_row(self): + return ( + f"{self.maze_name}," + f"{self.strategy}," + f"{self.duration:.3f}," + f"{self.visited_cells}," + f"{self.path_length}\n" + ) + + def __str__(self): + return ( + f"\n=== SEARCH RESULT ===\n" + f"Strategy : {self.strategy}\n" + f"Maze : {self.maze_name}\n" + f"Time (ms) : {self.duration:.3f}\n" + f"Visited cells : {self.visited_cells}\n" + f"Path length : {self.path_length}\n" + f"=====================\n" + ) \ No newline at end of file diff --git a/nehoroshevaa/task2/observer/__init__.py b/nehoroshevaa/task2/observer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nehoroshevaa/task2/observer/console_view.py b/nehoroshevaa/task2/observer/console_view.py new file mode 100644 index 0000000..6e17d87 --- /dev/null +++ b/nehoroshevaa/task2/observer/console_view.py @@ -0,0 +1,64 @@ +import os + +from observer.observer import Observer + +from observer.maze_event import MazeEventType + + +class ConsoleView(Observer): + + def __init__(self, maze=None): + + self.maze = maze + + self.path = [] + + def update(self, event): + + if event.event_type == MazeEventType.PATH_FOUND: + + self.path = event.data + + self.render() + + def render(self): + + os.system( + "cls" if os.name == "nt" + else "clear" + ) + + path_positions = { + (cell.x, cell.y) + for cell in self.path + } + + for row in self.maze.cells: + + line = "" + + for cell in row: + + position = (cell.x, cell.y) + + if cell.is_wall: + + line += "#" + + elif cell.is_start: + + line += "S" + + elif cell.is_exit: + + line += "E" + + elif position in path_positions: + + line += "*" + + else: + + line += " " + + print(line) \ No newline at end of file diff --git a/nehoroshevaa/task2/observer/maze_event.py b/nehoroshevaa/task2/observer/maze_event.py new file mode 100644 index 0000000..62ceb8e --- /dev/null +++ b/nehoroshevaa/task2/observer/maze_event.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class MazeEventType(Enum): + + MAZE_LOADED = 1 + + PATH_FOUND = 2 + + +class MazeEvent: + + def __init__(self, event_type, data=None): + + self.event_type = event_type + + self.data = data \ No newline at end of file diff --git a/nehoroshevaa/task2/observer/observer.py b/nehoroshevaa/task2/observer/observer.py new file mode 100644 index 0000000..76f00c0 --- /dev/null +++ b/nehoroshevaa/task2/observer/observer.py @@ -0,0 +1,5 @@ +class Observer: + + def update(self, event): + + raise NotImplementedError \ No newline at end of file diff --git a/nehoroshevaa/task2/observer/subject.py b/nehoroshevaa/task2/observer/subject.py new file mode 100644 index 0000000..984b39e --- /dev/null +++ b/nehoroshevaa/task2/observer/subject.py @@ -0,0 +1,19 @@ +class Subject: + + def __init__(self): + + self.observers = [] + + def attach(self, observer): + + self.observers.append(observer) + + def detach(self, observer): + + self.observers.remove(observer) + + def notify(self, event): + + for observer in self.observers: + + observer.update(event) \ No newline at end of file diff --git a/nehoroshevaa/task2/solver/__init__.py b/nehoroshevaa/task2/solver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nehoroshevaa/task2/solver/maze_solver.py b/nehoroshevaa/task2/solver/maze_solver.py new file mode 100644 index 0000000..c1e6b5a --- /dev/null +++ b/nehoroshevaa/task2/solver/maze_solver.py @@ -0,0 +1,43 @@ +import time + +from observer.subject import Subject +from observer.maze_event import MazeEvent, MazeEventType +from models.search_stats import SearchStats + + +class MazeSolver(Subject): + + def __init__(self, maze, strategy): + super().__init__() + self.maze = maze + self.strategy = strategy + + def set_strategy(self, strategy): + self.strategy = strategy + + def solve(self): + + start_time = time.perf_counter() + + path, visited = self.strategy.find_path( + self.maze, + self.maze.start_cell, + self.maze.exit_cell + ) + + end_time = time.perf_counter() + + self.notify( + MazeEvent( + MazeEventType.PATH_FOUND, + path + ) + ) + + return SearchStats( + strategy=self.strategy.__class__.__name__, + maze_name="maze", + duration=(end_time - start_time) * 1000, + visited_cells=visited, + path_length=len(path) + ) \ No newline at end of file diff --git a/nehoroshevaa/task2/strategies/__init__.py b/nehoroshevaa/task2/strategies/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nehoroshevaa/task2/strategies/astar_strategy.py b/nehoroshevaa/task2/strategies/astar_strategy.py new file mode 100644 index 0000000..2e49ec5 --- /dev/null +++ b/nehoroshevaa/task2/strategies/astar_strategy.py @@ -0,0 +1,61 @@ +import heapq +import itertools + +from strategies.pathfinding_strategy import PathFindingStrategy + + +class AStarStrategy(PathFindingStrategy): + + def heuristic(self, cell, exit_cell): + return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y) + + def find_path(self, maze, start_cell, exit_cell): + + open_set = [] + counter = itertools.count() + + heapq.heappush(open_set, (0, next(counter), start_cell)) + + parents = {start_cell: None} + g_score = {start_cell: 0} + + visited = set() + visited_count = 0 + + while open_set: + + _, _, current = heapq.heappop(open_set) + + if current in visited: + continue + + visited.add(current) + visited_count += 1 + + if current == exit_cell: + + path = [] + while current is not None: + path.append(current) + current = parents[current] + + path.reverse() + return path, visited_count + + for neighbor in maze.get_neighbors(current): + + tentative_g = g_score[current] + 1 + + if neighbor not in g_score or tentative_g < g_score[neighbor]: + + parents[neighbor] = current + g_score[neighbor] = tentative_g + + f_score = tentative_g + self.heuristic(neighbor, exit_cell) + + heapq.heappush( + open_set, + (f_score, next(counter), neighbor) + ) + + return [], visited_count \ No newline at end of file diff --git a/nehoroshevaa/task2/strategies/bfs_strategy.py b/nehoroshevaa/task2/strategies/bfs_strategy.py new file mode 100644 index 0000000..37c7b79 --- /dev/null +++ b/nehoroshevaa/task2/strategies/bfs_strategy.py @@ -0,0 +1,40 @@ +from collections import deque + +from strategies.pathfinding_strategy import PathFindingStrategy + +class BFS(PathFindingStrategy): + + def find_path(self, maze, start_cell, exit_cell): + + queue = deque([start_cell]) + + parents = {start_cell: None} + visited = {start_cell} + + visited_count = 0 + + while queue: + + current = queue.popleft() + visited_count += 1 + + if current == exit_cell: + path = [] + + while current is not None: + path.append(current) + current = parents[current] + + path.reverse() + return path, visited_count + + for neighbor in maze.get_neighbors(current): + + if neighbor in visited: + continue + + visited.add(neighbor) + parents[neighbor] = current + queue.append(neighbor) + + return [], visited_count \ No newline at end of file diff --git a/nehoroshevaa/task2/strategies/dfs_strategy.py b/nehoroshevaa/task2/strategies/dfs_strategy.py new file mode 100644 index 0000000..7f44975 --- /dev/null +++ b/nehoroshevaa/task2/strategies/dfs_strategy.py @@ -0,0 +1,39 @@ +from strategies.pathfinding_strategy import PathFindingStrategy + + +class DFSStrategy(PathFindingStrategy): + + def find_path(self, maze, start_cell, exit_cell): + + stack = [start_cell] + + parents = {start_cell: None} + visited = {start_cell} + + visited_count = 0 + + while stack: + + current = stack.pop() + visited_count += 1 + + if current == exit_cell: + path = [] + + while current is not None: + path.append(current) + current = parents[current] + + path.reverse() + return path, visited_count + + for neighbor in maze.get_neighbors(current): + + if neighbor in visited: + continue + + visited.add(neighbor) + parents[neighbor] = current + stack.append(neighbor) + + return [], visited_count \ No newline at end of file diff --git a/nehoroshevaa/task2/strategies/pathfinding_strategy.py b/nehoroshevaa/task2/strategies/pathfinding_strategy.py new file mode 100644 index 0000000..56abcd5 --- /dev/null +++ b/nehoroshevaa/task2/strategies/pathfinding_strategy.py @@ -0,0 +1,4 @@ +class PathFindingStrategy: + + def find_path(self, maze, start_cell, exit_cell): + raise NotImplementedError \ No newline at end of file -- 2.43.0