From be479d2c63710ae4978946a3fbb42b891257c15c Mon Sep 17 00:00:00 2001 From: Benjamin Adovasio Date: Mon, 30 Mar 2026 08:39:15 -0400 Subject: [PATCH] added TurtleDFS.py --- .codex | 0 Project 5/Stack.py | 18 +- Project 5/TurtleDFS.py | 256 ++++++++++++++++++ .../__pycache__/LinkedList.cpython-314.pyc | Bin 10445 -> 10403 bytes Project 5/__pycache__/Stack.cpython-314.pyc | Bin 0 -> 6084 bytes .../__pycache__/TurtleDFS.cpython-314.pyc | Bin 0 -> 15976 bytes .../__pycache__/TurtleMaze.cpython-314.pyc | Bin 0 -> 13570 bytes 7 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 .codex create mode 100644 Project 5/TurtleDFS.py create mode 100644 Project 5/__pycache__/Stack.cpython-314.pyc create mode 100644 Project 5/__pycache__/TurtleDFS.cpython-314.pyc create mode 100644 Project 5/__pycache__/TurtleMaze.cpython-314.pyc diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 diff --git a/Project 5/Stack.py b/Project 5/Stack.py index 8258829..97e35a5 100644 --- a/Project 5/Stack.py +++ b/Project 5/Stack.py @@ -1,30 +1,33 @@ ###################################### # DCS 229 -- Stack # Stack data structure implemented with Linked List -# Date: -# Name: +# Date: 03/29/2026 +# Name: Benjamin Adovasio # Resources Used: LinkedList.py from Project 4. I also worked with Pat Cohen on the project. ########################################## from LinkedList import * + + class EmptyError(Exception): ''' class extending Exception to better document stack errors ''' def __init__(self, message: str): + super().__init__(message) self.message = message + class Stack[T]: ''' class to implement a stack ADT using our LinkedList class''' def __init__(self): - ''' initializes an empty stack ''' self._data = LinkedList() def __len__(self) -> int: - ''' allows the len function to be called using an ArrayStack object, e.g., - stack = ArrayStack() + ''' allows the len function to be called using a Stack object, e.g., + stack = Stack() print(len(stack)) Returns: number of elements in the stack, as an integer @@ -76,7 +79,7 @@ class Stack[T]: def main(): '''test functions''' - s = Stack() # I added this so stack doesnt need to be reinitialized for each test + s = Stack() #I added this so stack doesnt need to be reinitalized for each test assert len(s) == 0 assert s.is_empty() is True @@ -107,8 +110,7 @@ def main(): except EmptyError: pass - print("All Stack tests passed.") #only runs if all previous tests pass + print("All Stack tests passed.") #Only runs if all previous tests pass if __name__ == "__main__": main() - diff --git a/Project 5/TurtleDFS.py b/Project 5/TurtleDFS.py new file mode 100644 index 0000000..8920754 --- /dev/null +++ b/Project 5/TurtleDFS.py @@ -0,0 +1,256 @@ +###################################### +# DCS 229 -- Turtle DFS +# Non-recursive depth first search for solving turtle mazes +# Date: 03/29/2026 +# Name: Benjamin Adovasio +# Resources Used: Project 5 handout and TurtleMaze.py +########################################## + +from __future__ import annotations + +from pathlib import Path +import sys +from types import SimpleNamespace + +from Stack import Stack + +try: + import turtle + TURTLE_AVAILABLE = True +except ModuleNotFoundError: + TURTLE_AVAILABLE = False + + class _StubTerminator(Exception): + """Fallback replacement for turtle.Terminator.""" + + class _StubScreen: + def setworldcoordinates(self, *args) -> None: + pass + + def tracer(self, *args) -> None: + pass + + def update(self) -> None: + pass + + def mainloop(self) -> None: + pass + + class _StubTurtle: + def __init__(self): + self.screen = _StubScreen() + + def shape(self, *args) -> None: + pass + + def speed(self, *args) -> None: + pass + + def up(self) -> None: + pass + + def goto(self, *args) -> None: + pass + + def color(self, *args) -> None: + pass + + def fillcolor(self, *args) -> None: + pass + + def setheading(self, *args) -> None: + pass + + def down(self) -> None: + pass + + def begin_fill(self) -> None: + pass + + def forward(self, *args) -> None: + pass + + def right(self, *args) -> None: + pass + + def end_fill(self) -> None: + pass + + def dot(self, *args) -> None: + pass + + def towards(self, *args) -> int: + return 0 + + turtle = SimpleNamespace( + Turtle=_StubTurtle, + Screen=_StubScreen, + Terminator=_StubTerminator, + ) + sys.modules["turtle"] = turtle + +from TurtleMaze import Cell, Contents, TurtleMaze + + +class TestMaze: + """Lightweight maze implementation used for non-GUI testing.""" + + def __init__(self, maze_file_name: str | Path): + self.maze_grid: list[list[Cell]] = [] + self._path_log: list[tuple[int, int, Contents | None]] = [] + + maze_path = Path(maze_file_name) + for row_index, line in enumerate(maze_path.read_text().splitlines()): + row_list: list[Cell] = [] + for col_index, char in enumerate(line): + cell = Cell(row_index, col_index, Contents(char)) + row_list.append(cell) + + if cell._contents == Contents.START: + self._start = cell + if cell._contents == Contents.GOAL: + self._goal = cell + + self.maze_grid.append(row_list) + + def updatePosition(self, cell: Cell, val: Contents | None = None) -> None: + """Track visited positions during tests without opening a turtle window.""" + if val is not None: + cell._contents = val + self._path_log.append((cell.x, cell.y, val)) + + def getStart(self) -> Cell: + return self._start + + def getGoal(self) -> Cell: + return self._goal + + def __getitem__(self, idx: int) -> list[Cell]: + return self.maze_grid[idx] + + +def get_neighbors(maze: TurtleMaze, cell: Cell) -> list[Cell]: + """Return each non-obstacle neighbor in north, south, east, west order.""" + neighbors: list[Cell] = [] + candidate_positions = ( + (cell.y - 1, cell.x), # north + (cell.y + 1, cell.x), # south + (cell.y, cell.x + 1), # east + (cell.y, cell.x - 1), # west + ) + + for row, col in candidate_positions: + if 0 <= row < len(maze.maze_grid) and 0 <= col < len(maze[row]): + neighbor = maze[row][col] + if not neighbor.isBlocked(): + neighbors.append(neighbor) + + return neighbors + + +def _mark_solution_path(maze: TurtleMaze, goal: Cell) -> None: + """Walk the successful route without teleporting and color it green.""" + goal_to_start: list[Cell] = [] + current: Cell | None = goal + + while current is not None: + goal_to_start.append(current) + current = current.getParent() + + for cell in goal_to_start: + maze.updatePosition(cell, Contents.PART_OF_PATH) + + for cell in reversed(goal_to_start[:-1]): + maze.updatePosition(cell, Contents.PART_OF_PATH) + + +def dfs(maze: TurtleMaze) -> bool: + """Solve a maze using a non-recursive depth first search.""" + start = maze.getStart() + stack: Stack[Cell] = Stack() + visited: set[tuple[int, int]] = set() + + stack.push(start) + visited.add((start.x, start.y)) + maze.updatePosition(start, Contents.TRIED) + + while not stack.is_empty(): + current = stack.peek() + + if current.isGoal(): + _mark_solution_path(maze, current) + return True + + next_cell = None + for neighbor in get_neighbors(maze, current): + coordinates = (neighbor.x, neighbor.y) + if coordinates not in visited: + neighbor.setParent(current) + next_cell = neighbor + break + + if next_cell is not None: + visited.add((next_cell.x, next_cell.y)) + stack.push(next_cell) + + if next_cell.isGoal(): + maze.updatePosition(next_cell) + else: + maze.updatePosition(next_cell, Contents.TRIED) + continue + + dead_end = stack.pop() + if dead_end != start and not dead_end.isGoal(): + maze.updatePosition(dead_end, Contents.DEAD_END) + + if not stack.is_empty(): + maze.updatePosition(stack.peek()) + + return False + + +def _project_path(file_name: str) -> Path: + """Return a path inside the Project 5 directory.""" + return Path(__file__).resolve().with_name(file_name) + + +def _test_get_neighbors() -> None: + maze = TestMaze(_project_path("maze_1.txt")) + start = maze.getStart() + neighbor_coordinates = [(cell.x, cell.y) for cell in get_neighbors(maze, start)] + + assert neighbor_coordinates == [(5, 1), (3, 1)] + + +def _test_dfs() -> None: + for maze_name in ("maze_1.txt", "maze_2.txt", "maze_3.txt"): + maze = TestMaze(_project_path(maze_name)) + solved = dfs(maze) + + assert solved is True + assert maze.getStart()._contents == Contents.PART_OF_PATH + assert maze.getGoal()._contents == Contents.PART_OF_PATH + + +def main() -> None: + _test_get_neighbors() + _test_dfs() + print("All TurtleDFS tests passed.") + + if not TURTLE_AVAILABLE: + print("Skipping turtle visualization: turtle graphics are unavailable in this Python environment.") + return + + maze_path = _project_path("maze_2.txt") + try: + maze = TurtleMaze(str(maze_path)) + maze.drawMaze() + solved = dfs(maze) + print(f"Visual DFS demo on {maze_path.name}: {solved}") + maze.t.screen.mainloop() + except Exception as err: + print(f"Skipping turtle visualization: {err}") + + +if __name__ == "__main__": + main() diff --git a/Project 5/__pycache__/LinkedList.cpython-314.pyc b/Project 5/__pycache__/LinkedList.cpython-314.pyc index 0bdd970d76e3461e46dec0863251d4f91e24841d..b0c0ec69c5afbb6cea8f23b3b18da1de68ed8a94 100644 GIT binary patch delta 14 WcmX>bxHxdbc1A|t&5sygs{sHpG6paJ delta 56 zcmZ1+cs6juc1CS;{fzwFRQ;sXysX6B%shSf%o1In{N%(OeHZ6oBO^=wfTH}Y)Z`Kc MQ~k|57+6`#G^yIb$w`HV3bg7GcJHe7Iw8Vn`=LZHPcNmI)#)}>0p*Y(}n-g0+) z%+3WkRf?-dja^$Ys6P~bYo-2z)T;jk`JulZA_`{350#o<_|3$$PV!UVoBeji#z3l! zv~OnLzL|M5pZDIp*(1$KnLz1#{7>UIaYFu$FJ6hbjkQT=%#lf=3h$D42qS5-iyR`qQ(yLCnG(^UKzob z-#5ya^}_px#q#bzymBWYYd?WkjcCFgxkQ+t5*C_FvhZ$V3aY@wtZ+;~#fnmvKr4)` zVhf9?B8z4P7R!<$fk_8RCSuE@r3$+~N~uX}gZZLnS&Dv*>E(h^o>4}x<@E|P%(B8v zWlCpEr%J)hS4(=CDHhZ6mlYj*T1s93h48@=f(JESQ-BPEj#)ApI{67K9=ILkyf6z| zUJ!msjtZbd=rPLMv660C+Khfju)`LknUEcwHfc#?b~2aaiWQ7J%jGPT?wYfu3pjs& z<+`27<+O6yWE#_RIlDbrP=Dix6m+xNfP6DY)=4OmeuCsLVP35PpX705ub}?$NNaCG z1J`iT`$8^+vgB7_7Fl7yU2&Rx&{Fn$s{~dhjAg=h#HvACQ8qw#D#0MY7cIRw z&0p*$cX6;VoU*rfjwYd|JAv3cH>|}OgRf&3e1&y9l9CItd(xg;?O#X-R@ys1OVAeh zk`bJVnsDZ_R>EzObGedPs1~se8+N6t72PkXTyEN+7AqQMy==ngSS|-3gSK3UU7#6k zt&z`Zj8S8%%5)2ab~J#J*Na79c{n+*nGi)SX#xnx?DT18Ww3LEBEfZ9?LfZ$6ddcJ zIJzDJL^w8Uhz(y0m&}8ZU$84~)f&2(CTO8KqLSmWA;AnC6HEb}GAm?5$3m)rcCUsm z2*>3s#B)%Cgi^K8k!{KHOzty}aNK0NjbSZfJn9d#>!q zu`DV3Je9j`Z66w>p%smqZYf$>(J_*^Q;z}s2$CJi6*Q*Z5h%Kzg+uf<7eG=Xt;xo+2t zvaOz0L0IVkeh@uk7(Ia8-pQ*#MUdhJm(I7d4;qcznI+?%u{n~QdT|8fM(yufv1nek z6gI0XMZK&{SIhYz1}k~^tQTBdHHE9Hm{Y&j^Q=$N`)B(5WW^~K56~ly$l;8~u29&3 z!!Yt;E|AH{D&HDwkV}-Sr6~yW=Cq`0vx`Aeb9jDx(*Qc zAizDhcdTM{i}5!{$3_@^xQC%9YB&z{vt=Bmo+*t-i0f5XD^+V29ZH!oJ_7i06^MpL zrwm3ldR<}HE4pHq!R38ab%9il)5+L>P8&b zi?_iQlLUGgwKxXkWr6HRjs4@kDRTG;0dn&^uL5mAiWeWjYw*eu^z6?SzzPo>EQg$m z6yR*@AQydHRX-t^6~G2WSFE8L$5`}C@kRy94b}ce^^*A!z>m-}vr;lG<~gD=P0dn( zIiOomoT!H4nlHBz+{S^>82UCFldNinrMs3Bq#Te4dg-4r-7v2KDI$h?`8y|kD;^^w z+LB@A_(y(jy9VW~i5t|W5D~7J6}!o`BeTL`L&D7{(Xu?UFtYsiAKre{+EL&C#{Jg* zdZK?NwR5?)PpE^{R4h4XEu>HX1^%khuXkM?QUA=x&<5i%I2eofrOMGcy z9G;$@1k|1({2%5OZ5YmNjyG-Qc{6d7`$u3yFEqKU2AegjOo3F=n7)pgMG%5EoFYG< z)zLv%P$oi$plYx(5#yCD&FGA09GeVHtmyh>KVm_fLx_Rx#iq9W@xsRsQitxP4&6^> z>QW{E*n@GSxe3oXrQ9nIk7kgGh*PFn+%^^AXVO{FF|f5rr27d0a@(l_RX~at-`1S9 z1*zQjxyK|q`3~VXm)fKO*S|bmr!3{_tPW7p0A}CWH%6HWTGf>;qeP!_PAgh)VBt7d zA|PTmZd`hfD<&$6puEO~0UjWBSEk z`BL2UE84KDUit;s$8H#NER&+R_Y|{ox);f7Ky0`#Vg{0{V9EhkL5k@Tzbn$C*gAn^ z1j$d4a5u#%DW-jNKM**>;p8NCqQ7AvssM}Mflqr`Sr>39k~aMGE)cjW{tj-6XTOey zB3 zPHB+;jN#l0&Wlhg?&a0qKkP~=eEB@%LPR(krzqxqTv;UF&XJ`v>ts-XJ1vkWNE}On zkA4xM1yhJ?b%-I2R|PLfR~j;mEb*=i;I8mDX$E@24;8}mjfg*{-|)szh4(hfZjpyf z%HG)Nt!ETA&A83cGAVD8-fGUaU`_~6TS;^V5<#Zbun*x)^K7S8=O*d6zgd36+qKD8 zlWu#Zu{ZC}OXIf>Zy4L?o#qyykc;SLyhKR>?5qR+S?(u47Y#BL-cJl+ADJNAr3WuN z&PDtu#fusW&{D&A7ppbHF~a;ytTj<tNi)iH0wmA}tslXG06!6=g~M6eGzMscAW~5Lt??NNw}gZyv>W zJtaaU`A|x%hN1p$HR=jQ7otnjbA+BoNkp#82fq}%t!~)5yKSv!d&%A2_}PK*-D3jM zldYpN`CN{V?g@Y1E&#>Xa=GoHz9?AfD?%b1dzL< z9*m>V8IMiIP9-&VMNK>rm86l}x>HS)>3G_v(x%bEJ-tPW)|L@&f?<^|hkWPH`&yla!aNIBGM>-W406%|<=eQB> z2`DwNhUxewS5nQMXp+D+j!S!R5XRz?BSM z;j02%&EN`O4d7Y^SNiGz*E6`v*8o^zaCKjeuW{?k92W-8wOn6yBiEY3wE;71X8#h` zS7_zJ&r=JO7HBp$I}yFu`s}Ud)chQ*v=F8C%$$o@nFD1-nKEaewUO&{_7(Lxe9kTD zx5YlOPw1{s4W9GxxmM#>9e3dE2=}bHj*IY~rjMxJ!|V27EEd;-S|lD*@l)syY6D#W zT!$iqiKyHa9F)~WFeIbMa!3n?j`y^Oa=PZQiIn*HYUCqaAE)s?PBR^;(9Aww6MQDk zLW|%LbXVYzHgvQ{Rt6)npcYq#*S80w(WB^6QshJwV+_hMO*$4=ByC91qOv#J*83jU z<+18wAP~cZ0s*}+5EzVyhoS^K0)a0M1*54NS0HdKqNrLl5|d+bR2KvS;dlr@c_5$? zrlCMk)0D{3Ax%~T0mX)i&l3*1qlb;VyZw+iaas{k#Igziss-8W+|R^qiaTAtU>#B< z#?QB-I%gfKX&tb6CPplR&kR^#u*GKqY-O<3R{$8~0KzQr*+A5VSTnnBXKQvSq{wnC zL`$7oZjOe-zVq|7Y&bu6#I*G_&>+fcxzF+gypQ*pp5mLi_skzrTivS2Sk;&=bj4#b z!x=kj#bOTzHI;g8Qi`b^7BfW=w6v8u<9l;!z}1{uifn|t$MLq}JCt1$*n_PQn=R^D zqW;16ws|Xn5zfb9UVImu#d{`QQ03?`T?i`us!DTRz0R0QsVZwH<4QCfipP~O5sj?s z4Y@PQ(|{&6Pn$Ku-E_FKQ+ectQ_9gvYN3=Wz&Wd=5V0#X6mi>@5=21C$b?oMYhe2-Tes5jb+ z)y5uMhZ14z>p~QbG8*{Fptuqv%`YbA@$jcXQCYVLL2)n`iACe_gkF}ri|JAtI*TNu zgD#IvA@!-)DRWR(L>XHT66DHy%05Gxi!w$$OezrvD})Hkh)7i;($~4WCa~vSYdX6} zP!>JbI)sLqL{x~x&;KLx9BH=3kY*&;A+I3IAg?SW&q$U5wvs#}S;piTQ*R80JVUvi z!8RXLW(+R$IZ^Inu-)ed426w%9ljF4P}YEpNMVz*2J9q-P0AXuixf5~Yrt+&*rcoh z7n8y!WevE*R}ZD%09h)fWT_$I7f2^UmeDM2#t7F-fc6fxlRlVw)8%KBlQRnZIms6rVGmET{Cu&+k z{Szy*2AefN#Vn+L^!C4Wb;orLe+lJYC@vm&BEF&uMgQZ7C zCLzn=MaWPQ8{&x|^EBqYR2na*cNt}8^={5)Ef!U#p@iXFg2Fms0@` zFI9wa{A6q)?@nBH}SCD`k1|FD-30YehKMDn^idY1Or|VC*&t;KW%<0z7OJ3Z4gOV{ z-s{|N;9aiGdY7LPBB#_?TZSeZ#ADX2hS#85jVPF2n2ieRR*jJcS_!*kh+uJ7Jf8GK-oH7pj^fy&_SBcR8BZ%hNMo5b-;T19*;3 zeM0nGx+&(eDEY;7%LdLbc9$%w#?M}7x~#h_4P~F~Gy(Y6a)(D^8T=mDQ)RY?G(wCD0!aMiDEckfL&i4Z~(YLt~iuNkm%0@+n=QlK@?$f@nn5bQ`>j zbX8~|sOUlnvO^^rTfJH`5(`c$Bue$-9Bwc|H;fbh0(B$Y?D~zP!duSSwq5l2Wm)BT z{dC#dQQNJCwO6Z>p68}K&rN%FBt0Edo{nix=Z%H~V=s<&%r>tY-FK^b!_}VeJ%8SH z&UUM!X1wDEYd>iHzIVE!?VK>{uDswn@0t*1+^cSvR$SO|e#duTzH;R9k(tt#*{0QR zcg|)C`!4t0DBU#Mxav>#-*Is(Tjol*qT;h%V_iu{<8~?OGsF zC@KKr;OAE|Wbz*(_{=`RXYpB|qEnm#LwEi$+OsXw%_oAhf5 zM)?XFX!utfkd;WLE}^!Or3S1A_lj}+vTw+Z(um(tCYQtOy^JO z{AtK^M(1qh-$AiTE3tZYDva(lLgwA6LG_9Z(JriD%e@hAj&Qf_&aXW`TPMBMH7h#K zI>($zv2IGN8`mzKxOiehyK>_4iL2^leaj88)mVBG&1s=ci;4+6%<)s!+A^4&_zWgY z6&_1YREQ#msUj1Mx75JQveYnN)KZh32pX|O6T(DnspH4|(kvaZ<&`lsUmB_Hmo*%1 zLb7CeJzd0R$~TGK!D@fEn{1NRljh$^>?? zFsgw09X_QbXg(w{J}D&m{CTqE`X0DMBX~irM#c^xEzPtx&Ex7EJ;*A-!dR$$}9@nkyqVF8!u_j)LEso&)9%_G1Py$tQkzw~yVIkmO)! zKq9{^eiYVcC@M=aa^#NUmP#as3Pl^(AgNI4c*sFj+aR5U^CscKf~08Danw9(cbqfduzTJPjjz1qz381@;Yr#( z*X^FG!D}ryynFwy^n;!s{^8%&C%t>Gd-u*()Vy5kff`qvf?BF+n=OOVE0+e1p^H=E2n7{c<-t^To@X0EJPkQa?5&b5*Y{YYEUV7Xk zF$3E?+;%t^JuczUL{f)BAz4+A4MimdoF`|jYE6#H39uxytYcw`og+yRP3or$RNk(S z{tpRE$?R$yn4~+XAPQqBQ09F)y5VUCUThC^xAp9U(gg>|!Hc~Y#M5^Ki~&xosLORAD3%~K`K(Zs zv}LMk%MEes9Wz&4|FM-Tt{ShKP_LRMtJdFeZ@6FUd9>bW5F?RSn#2Uak7~wcna2pK z3PpU6iunD1>p$`ScBRZjN?Mzm8I}mlvfLQ<^`}8`qr)D`lJc`CfPW zSH7T8mIpCkx}CNtrSSo~v$Tirhn8m&ge2Lblx_`)Aas*AwkW0R)4hdZZ$g*TeIk|) z3)%S)hx$witigU*TnlM{I11C)`PRysf2|S)ht+M^+mw9)$pwv1ruU-c&jj!qxE^c5 z(!d=wF)eP9Ut_rE-#1I%eY51BOU}OKr<0t(7y^P1vzG90u`l_lAg8y3(*4>zET!z> zI}wKgy)-UbIQ&fSa6&x%>eiF6{yPglFA#PGIAW9nuc*y}=KNFC74!El(_dWO|^5!FUa)6(PX*t1&~BWge% zOlYUkUY3vR)`&_zH`52qNYicT%+N`?Ie|Na;_f|dy90Z=b|cP73P`sYAy9gRj_U=f zXp{0os)OlxN()dhQ~5sqw&&iP6~h#FMPStkbf)5*lA)!{;bW?D9q%xqdkc@ zJveso%@;@Z+^VYoW(6KA*L-IWl$2n5WB;rBM~7yF@>#+DM(3-Y=gKcsoUa&Pb!pAT zHIwe98KHSr5R*d1lu$8ldH2+%GZ)Xy2;SSy(zCI#SkhTP?W~_IskpH2{JL*!7%jMM zFFCtoY)8^wbKPEZ%h7mhbOBy zjdsqus&BYFh?XYZO;hfsi7hkk^|Q4r&)IIe%kS8^s?Bpw7>d{J(yXKOZ0A_#c-5tv zi!~FGE5|P%pRC+8<7k<66rbHcw*TDe8As!7S7p-GFy(4Ux*D&$8fV>QNq6&!N2JMr?Bz~w-4)ic*uJu~ZY zCmjt_KuCOlZF1eSQ|q3caXfe1RdyE6KZg3y)sSssxl(w!@H>vAW8=^5d9E#k1s+*1 z>vWgKIQs#WYKq}yQ%?_6&NFC~QO+i^XOWE`nxn@AlrtVnl`|<@{996IYuS60y(xx= z$N_0l%4dS6#Zg{WoOzQuXB$(_Sx};2KO8YN5|){1*sa9BAcr(*vlNbijq#bmX)i)6 zgfx9Uz!n6b1Sm2rvJ*Ispm;1h9YaWxAx#rZ#dL}S38Oc|YSU4}nn(afxL?{`Z;Nk& zY8$8Qjnnq#q0w0-NOur<3nDU2Wf#{Pq^Ws}Eo6aSeIp@na~!yzNP2@hj% zG{~fskHOFXD~fWjxMnRRUuKEZo0K4Fo5xp3Z&ZTM9;6-S)7F5Sd&x9p@^V9_cFgmO zP5HbD$o)LFK#Qr%)LCnI7YQ?BGiIO3XZD)1hghHG=);C)^xc5}geijWNRpl?zNY`eQ;vKblFqW_Roy$2E%8h>bCZO&}OzB zny%@Y+IHw#*(Lu)|3uFf|7HJl?FN)g3YC*JJ-6)*6YW>qC!1Sl>@AZ*%XNFp|NO?n z)xN|tSL3gwwnkxr3rxHQydNBze>gh(Il5CDEl@t+=eiYV(Eh@!)mZHX%}W^Mj7h0;S?6sORE1r@fzD`91>&Cep>!K6tAn| z-J*%ebmeEJT~Ch+w~EWo^?ajh)Oy=hK3?|bnMtv3^b8$|e0l84Z+<1|T07-h`~JS< zhPJ5aHBy`GSdeU>L(%!tk)SjuIuEYtZYLR6A& zlYKhyjfdal;5OxWTo|h?+ae8hlek!vmh&kLyd|!e^cqX}R1$D4moVqd%i3ec=s8|= z9r>K*=bD+mS3qL8)X#d9XzZ{&7?;p&*tA_5Zg{}Z9;ZS&gfXRVN#O8U!>Qa$zMcbn0&V`b zjstBk9N1%MvSDiXAu?SIE5VZo>12gugDw#7>%7Kpim^|HblaX&A$T(gLP6xbMK3aq z_iJiymK9}@Zo@H6B&PfZwTye-0{~?vRLlrfw}hhCI&TTK$-*@=!rEWkYo=kgi$#;p zFU*L?Z;H-ey6eWH)9&>n9do7v%a&WNs&|{mBj5B+yF4r!b+%}%=q*=9pwO#iWnbyK z+yymvZPT@5*Iu4T}rQSpgdh1ved(&l|(Gxy`o_FYxxpK#7yW#*rhSpbtANXP{Oo?7~j^wO)Ug{%d=fGo;r)ZqZIBWK|)KRfwGx_{yB}-_*MFAo~clE3UQ2hhpJ9 ziV|0pIjR;ZqhoBPg&GzZU#qId!50oBQc{~DfXwwXoIobnbt=n{0NVrO0FWdDlUIiM zAwFkxx=X=GRfp1)hd@y);DDu2oq{)}t<6<0AQbntxhPlaMc+7UXx=QDBq zrYY{}d)6i%ho{Ky?&ooac6T*`)ORHW;qHEphnau35C-L4H|hGjWssM1YdOI&Iy_^p zx+#dSw)KH5ol~$vcleDIn6;^-_v4;UoKWsGy3`jo|+lOJ37RlmnJuJYwbs6?Sj^gIy z_WPgvA{jb%fb2j#_dNdR`G21OIoefO;pY%qYX3g5x{c%hoibMN8VP>$G7@v#C>Q2m z;EwYezn#*q7hK2PnwzEFF9@1Y&$V-5p^*!F&U((e_X$R8&CANYC@kpUjKf`fx%&V0<>ucxs8I8E@T-e>ge!;-UR2Jm*@@OiR)}mS>ol=n% zj-{rj24iLe_A!U#HxDB_$DQRgKFn#Z(Jh)g%xgl}rFp_`%^Mc9vam-J!(Po7F4O#B zQ7aGoFhNWzS*-=aObn!?!rO7Exo&E2aB1bh@il!tP1Ir4Vh@_>ZxICUn z$eG_ZC#FVwB2CHB zlq#jtQj7+rCKsg`D$ydEx~NUk_-XW}__awCQ@`m9+g1sMvR~&Voqt*9pV#?QI^U=B zyL5hcjK`kRj{IF(u04DQcgy`T!RlT`)~1z| z?#7gF5i-44*i=+gsmC39#n7~(CFOzWtlXEms0Shu6W4MiqSsqE=*usb&`_;KG{=3$ z@t&4@6lH7KXA{L7n=mCP&@h;sZ?gpnnsA6pivp(04`Obf^U>tAdJbB@(LD0HQfAS5DZ*^)z`Me$!9c&hT14Vf(y3=A&u%Vl=p~sLfEbR?5nZnX#VJ1shn#qxHIZOM(0ko}P zX@9s9=_-~k4_70-g{1@G8l-Dkx*}YsRfg-q<_)?)>^XZmmW-<4En;LaTzW ztFpj%3l>6u>ee{y;%)hl%wY-ouCe~&7o zH%mAeL*`4A=vex^9P}6P8*L-Vm*4DUyH=Xt!tSsuEQCFKAUT6xr5d2R8&lEUiIk?h zF&|yDW?`V2C^N__b@&*J$f#Utpq5)`OA0t{7lE}SB90S0_4g>s9@(JJQjC4xKf?+c z?qdQ#tzZfa-{yXl%yFk5Pgylw7~Gh$1PgasN|_|Wai^iskVabpB#cP}%^ZJWkPGik?KI(nPut3?2p>CQ=CvELCAH(a5sp5yOImJ9b^J z%(b=83zq}AEp?Z@h<@ZXRz)+lW^1nMQN$oaheAB1^ilK$Td$B3d;&jPyp?%c?{SE- z``E{s2!L3i31O=$9wOd?{OmtW$=YNZ#F~~2k<7%uv5BOqpdD0$*3)C(mSbQ96$(z1 zl_^N1xD=lzjpLUPrXZ{(`9da{HdLWM%tX9;fYq8B&+H_YRuoyyq*HOyJs6vG4%1GI z*?>+pVKNboPc^I;V>kw#HvWM7t_88Sqm9-QOR&@=h?Pa0(_3! zsN|~auDrJJ+OoLS*-;C%L^F)zBnJFIjuZB{z-=Cb+q}j;8W<*A>%w33>vrYCu@lT%K! zcF(@9${KJBi@_p_=?ui1?2tjXDjAFNV?&|60BvH}C_W%TASTAgApuibKH>Po(S$0S zu*0NKk10x8krLw)X;tajXRVHBx0}QQ8wCa)nJEcPmC`dQ6Jjvv()~^s4Cg_Y!Rkk7 z>u6gn_V!^(m@3Hx1NU~G?Q%2QEZ{y+efi+eT9*3;K5Rd=y!BMBvF%5J&y&F;b9m65 z$KgjY$U~7%KsJX9o;@Tv_hgTe=J}QkR)3H|zW$chL4OStUZ32NkG?|B~$Cm(X$+wFhba2 zyi=ebOW}?nnN}t62O_Jx6tcVt^?5YX%NfHN8Ma4_fQGJf(DYv0yKO5~eam8>vrk_h zNmVq7hau?8i4 z4>j(+Wf|&`=)EY&e@HrWm=6o3>q{LK&9O@K_JEmr_Ii3NoPjYHlFQ!N?;CCfaCjpN98Q4t_*M_sfvd>616p8}7g(cjXon^`e=y2;q(#yuNWea*%aoq2jb@fWK zuAnpM#VEDgOk^6tocqwkI`ckX-t@cY^?U;D*tzr6H|OUqC7e-K@6IKCD*KHs0Ks-LfL z2iSro2tvmSVNTUU>|320I^_;o*l?C-C<431fUODa1@w^_$`iqijrWKGLGk< zfJg*t7;*&1(hSUR4Y=)<lo=buTULi^n;lVUB5_`Z zKZ6+|jGmyrWb#fKM>gcBw2%rMMP^nr2}7zZuOHasOb@C!K@=!9mm^**i=LsHrfymE z-bh3~hvT}T+l&m`c{tOib55V-iUkNMbekIi7{W=Jh z&uT21GJ2)HV))s?L@>We$ALU&+B7-S;9!Tx8!>#|h~f1{48J#GF($QIg|s>8F9p8wLaZFEbcS=%J_!8AsW3X z4EwAjFIYny5v}Tw-%2@Oc4P+HTFLKZEA!Ktl-=*?#gyGcL2orqFII~6hL-4UF=dZ7 zTsF!Rc~~J+c47*iXl@%f>?*1u!^Q&3`<7g#*IDHV8F?%W+oc)ziN7>P!OGwiFarE; zwh=Py4#I8UTW-~Hgt1C1hq)Ekk4eu()W7z^YWu zB)rS2?!Pdk;OZ%f!>sPVXs4BLQ**B&Ky+TyJ?dmMBkSImVf>;q*Gx*Ug_~w3tt8{I zH2mp_6uMA@e&*xR1GIwvL{c7v`q2F}>Il<5^17EX36D_VfMKo=-_89-0MK4QLL z-4&bE1(-2Om9W^=MNUi#hTTfyUaxh899-XqUzpUinuK)$zi@rSyg%2}JYSJ(-U{b% z<=xsw7^krhYag2zS>@uvG;BiATxG;uAROVOWhn}3n?+~C6CdZ2wJ(7x2Y8hGq(>-MFxcTWDv zz`YKxzHOrmpm1q7cOlx^lkL0t#9B))R#25g7w3Ko7tH0VyIUHswY}N))^=pPIWcg> zyWqVNSO_ehSrxb4t!cWp`_0{N?LlQ3RhBK3t&1HiV#m_d`_Z+ouPt}(zx~8o=QFpJ zwa!Byd}}%U@~=mij|?rJKC^b@%-ZWSMlp00+5BW}R>I4k-RXW>d)4Gt|Tgqkc!8 zKsE?gx$`Y0Uq?Bo)ril6Mf*GnV3k^s&{tQWulNFZTlQMeF!#-M0bT_>Nqt-HI#9!W zFS&%xFzm|DW!%H=5Shb8VZ@fVp}L|fWJy0wV24_{69-A!hitv!z@C;uO-{~A6-X;S zNNw}xRtgGQ-o(Xr?!>gAiB4QK^&S_ZYnq~fKDQSYA!{9ttUY7q?&ra{5SegAA?$i+ ztB#^3$mZ)G%&8Q;kU>`zta-Bem1;xuTNFlZ=RBmj0BP>|n5awl;zlJkA?u#8WHk0F zT$*s64+d+MS*rA?8Ci}i-(`TB5{=1wwY5%Tq!Ux%;P#4HOga%^s2ZLojHON zKV4QHPG3-FsOkb$RT^IZP+CpUGXP!0eaXut_;n8iY+BKM<2XecNv{EGU9}ycPMC|} z4Wx{#HcO`JA`Qs|b-rM=3jdTE{1+k!);wtwUKN{60du8tp>pxus@QyYOT(ge?b6jt zceZrRAG%xLlHGUX;Pr!d>K|L=bB$Z6W+}SX*mHBsTH`Lr*V@Kw2d^Id*_rj7Pp#~H z>Q3!bxw@wHx}KFfK>R#%E3y`R=JvVUV;}7MyXSuO+}gqL^3cegy0e>QT*Iz=Zmy;q z{xV<1y0~>k+?qXlW8nJ0JEvB~-uzjfUKQKS{JPk*A~t0kRz)dq-o>l9V?GBcxE3YD z7Hs0%B=7N;5o!hGhE9q@$Tpds8kpW(p+Qrax6JYfvRDZc9?mn?z%0&Sb}D2GPrHbF zgjRWGsnu@uTMuPt+qkvbI0EE>tFq17Qf)@ISf!(aH9EvL2tGsJLaQ?G8=_NSQOIZX z2eh)ySEp(IdQZSSVMm72W0nx)4uu#uKnjjhIPeAxDdL*W=)y!=ODk`oSa}lvg5EAy6jY-F5;o_4(lmd_^d699m3?A0&}QXN zE}YEP+zGVa^KdmS8(xSCW-3)JRM|{!>OQO^&vJ<~Bk}htKd7uum~80>v{xt5$4tkGRwFJ_7=dGF!Y&2$bjbo-%m+9(`5M)JIVZ>Ba9?Om{w=Ci1<@S$Z{>JZC0!mQ8i2IS3OBshy-UJ--gmvL zV)sYom2dZ~*L1Jcbg!0o=jygD9LrTyzkU4L;MKv^iXBeDdfm>Ix}B>PJ9Bl-3&%dH zYnngyyYgyC=k>t$mB98T&z(RQ%(%c7=M-t4xE}rCszSW8>-*6dJ$l03jq{Wv?+F+4*MHG-E3(6JuL!HY z(1A~``uxV`D`p2=!YcrF|48qx2$99NHp-hQEbyIJ=LfbIMCg{A{yPibSr%KIZTRZ( zqWl8G-N7#p2Y4Lp!Q)`Oq{7D;96&5QIu7Q(3LJd?F~Aq0fS?O7pkmI&7|_eGiy?KXxHHMbF-+)#!wLz>nucpwnk1XB|DTKk*=z z%3s-cR_b6kN$~7Rpj6yq$rq%3QV(t6A7BJ3PZ*DwsDa(=mB+1??0vQI_|OC_?2D+J z&d;XW!|U1jg#xu#9*JOQ6Pi4QOQ{BDp70pj<^PHfOak0!FrUa|V{27Amc<>60E6WU zLnJ${vx7V1EOu}|hsH{VqFIW*OObt8XOCcmm5M~A?G){xsDmPQW%UzEk(sS9Ax~ml zArZ~ERQYR4kvcAUWi>$+6w!>-ClI~Ced22OoaH|`&Idd#pR|@EYH@p>`^3+CwtecY z@^s(tgnXl$tCrtamU}wb>R2B!G`E9*6ljaz{1geiU$Yn=n$$8JGo;%9Qyli016I@kCAZX$v*9ZHW(Dy9w`i4HhOdq zyWlr$FRyC2$KlmZq>|}$hW_b=h?+fhHIy$EBvQ(+u=@YOPaQxs$NjdvelhuBc?aI; z3*rw3zBlmtsST0y_|}ES6`>Jox8i!mJ5{Sf&t1Vc-+5*C!tNjLSruA70&xHL`l-RX z(6S=5+!0zCI_X~*Xq-qSSWf@EFg~qKEBNOHg|MXy^m<((1T#m!QbQ@SvyAI>MpHCF zgJf$dMJ`73HTE;ce?lBoevLAs3iTyKxTxj%`!yWj@gH3MZ@7w2{ndQSyt;VwQx1vG vdQS2@ZrdsT?2lY3&*S+8#h;|ye!gv^g%bkvv#ajre-Xqt`e6zPhSdK*v6fsA literal 0 HcmV?d00001