pax_global_header00006660000000000000000000000064112473132450014514gustar00rootroot0000000000000052 comment=bd1732d95925f70a6f4b6fc33cedc4cca014371e rurple-ng-0.5+16/000077500000000000000000000000001124731324500135155ustar00rootroot00000000000000rurple-ng-0.5+16/.hgignore000066400000000000000000000000541124731324500153170ustar00rootroot00000000000000~$ \.pyc$ \.orig$ ^build/ .sconsign.dblite rurple-ng-0.5+16/README000066400000000000000000000037051124731324500144020ustar00rootroot00000000000000Rurple NG, an environment for teaching programming to beginners Development version, Paul Crowley, paul@lshift.net, 2009 http://dev.lshift.net/paul/rurple/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Rurple NG is a reimplementation from scratch of André Roberge's marvellous RUR-PLE: http://rur-ple.sourceforge.net/ This is an early release of RUR-PLE. On top of the failings listed in the manual, the biggest thing it lacks is a working build system. In order to get a working build of the Windows installer you need to follow the following steps: - Build the manual. This is all the "SConstruct" file supplied currently does, and it currently uses a Linux specific hard-coded path for finding the relevant DocBook style sheet. Running an XSLT processor by hand should work equally well; the manual should end up in "build\html\index.html". - Build the executable with "python setup.py py2exe". You will need py2exe and the wxPython library. - Run "build.bat" to build the installer. You will need WiX installed. I'd very much like to wrap all of these steps into the SConstruct file in a way that works under Windows. I'd also of course like to produce a .deb file suitable for Debian and Ubuntu based systems. Help in this area would be very much appreciated. -- Paul Crowley, 2009-08-30 rurple-ng-0.5+16/SConstruct000066400000000000000000000003531124731324500155500ustar00rootroot00000000000000#!/usr/local/bin/python - fool editor Command("build/html/index.html", "manual.docbook", "xsltproc" + " --output build/html/index.html" " /usr/share/xml/docbook/stylesheet/nwalsh/html/docbook.xsl" " manual.docbook") rurple-ng-0.5+16/TODO000066400000000000000000000051501124731324500142060ustar00rootroot00000000000000Still to do: - fix build system - fix license display under Windows - support multiple robots - change file extensions? - make robot look nicer - icons - adapt old manual Enhancements: - save state on close eg speed - look into using process instead of thread - gradual movement - follow moving robot in scrolled window Done: - switch from thread to threading - pass return-callbacks instead of queues - cpu tells main thread when it's finished - abolish CPU.AddFunction in favour of SetG lobals and exposing proxyFunction - handle exceptions - move timing into CPU object - make proper Play, Pause, Step, Stop buttons - make Play and Pause into ToggleButtons - make Play button work properly - make Stop button work - make Pause button work - make Step button work - read code from editor window (initializing first) - highlight lines instead of printing - make speed control work - move world-CPU linkup into world - move window creation into world - alert instead of printing on finish and exceptions - log window - "print" prints to log window - make world loadable and saveable - make program loadable and saveable - load a program by default - add beepers - world handles WorldExceptions - make walls work - add World menu with set beepers option - Move json into ui - new world dialog - start with walls on outside - only internal walls editable - only internal beepers editable - on_beeper, got_beeper - front_is_clear, left_is_clear - facing_north - no editing during playback (world or program) - disable world menu items during playback - ink trails - roll_dice - new text for title etc - highlight exception line - log exceptions as well as displaying them - style editor and log window properly - add menu items that mirror buttons - add save as menu item - add save menu item - add open world menu item - same for worlds - add open example menu items - arrow keys to move robot - add accelerators for run menu items - add hyperlink to help in about - move maze into worlds namespace - write __name__ into world dict - tighter redraw of world - input_int, input_string - use Refresh on windows - use wx.GraphicsContext rather than Cairo - add save (not save as) menu items - fix scrolling world window problem - fix sash gravity problems - useful status bar info - make installer find share directory properly - style exceptions properly - filename, modified indicator - make modified indicator work for world - twiddle files - make Save As warn if a filename exists already - Report errors on save - Fix error reporting from lovely editor - save on close - rename beepers to stones - Windows installer - update scrollbars when world changes rurple-ng-0.5+16/build.bat000066400000000000000000000004631124731324500153070ustar00rootroot00000000000000REM replace with a real build system ASAP! REM C:\Python26\python.exe setup.py py2exe "C:\Program Files\Windows Installer XML v3\bin\candle.exe" -out build\rurple.wixobj rurple.wxs "C:\Program Files\Windows Installer XML v3\bin\light.exe" -out build\rurple.msi -ext WixUIExtension build\rurple.wixobj rurple-ng-0.5+16/experiments/000077500000000000000000000000001124731324500160605ustar00rootroot00000000000000rurple-ng-0.5+16/experiments/converter000077500000000000000000000014141124731324500200150ustar00rootroot00000000000000#!/usr/bin/env python import sys import os.path import json def convert_wall(x, y): if x % 2: return (x/2, y/2, 'h') else: return (x/2, y/2, 'v') with open(sys.argv[1]) as f: p = f.read() d = {'robot': (1, 1, 'E', 0)} exec p in d result = { "width": d['avenues'], "height": d['streets'], "walls": [convert_wall(*w) for w in d['walls']], "robots": [{ "name": "robot", "x": d['robot'][0]-1, "y": d['robot'][1]-1, "dir": "ENWS".index(d['robot'][2]), "stones": d['robot'][3] }], "stones": [((k[0]-1, k[1]-1), v) for k, v in d['beepers'].iteritems()], } target = os.path.join(sys.argv[2], os.path.basename(sys.argv[1])) print target with open(target, "w") as f: json.dump(result, f) rurple-ng-0.5+16/experiments/dialogs000077500000000000000000000013031124731324500174250ustar00rootroot00000000000000#!/usr/bin/env python import wx class Frame(wx.Frame): def __init__(self, *args, **kw): wx.Frame.__init__(self, *args, **kw) b = wx.Button(self, label="Press me") self.Bind(wx.EVT_BUTTON, self.OnButton, b) print "hello" def OnButton(self, e): d = wx.MessageDialog(self, message="test", style=wx.ICON_INFORMATION | wx.OK) wx.CallLater(2000, d.EndModal, 0) d.ShowModal() class App(wx.App): def OnInit(self): print "hello" frame = Frame(None, title="This is a test") frame.Show(True) self.SetTopWindow(frame) return True def main(): app = App(0) app.MainLoop() main() rurple-ng-0.5+16/experiments/dirwx000077500000000000000000000001621124731324500171420ustar00rootroot00000000000000#!/usr/bin/python import wx as dirme for x in sorted(dir(dirme)): if x.startswith("LIST_"): print x rurple-ng-0.5+16/experiments/except000077500000000000000000000003031124731324500172720ustar00rootroot00000000000000#!/usr/bin/python import sys try: exec "\n----\n" except Exception, e: print repr(e) print dir(e) print e.args t, v, trace = sys.exc_info() print dir(trace), trace rurple-ng-0.5+16/experiments/localtothread000077500000000000000000000002671124731324500206400ustar00rootroot00000000000000#!/usr/bin/python import threading x = threading.local() def foo(): x.st = 1 print x.st t = threading.Thread(target = foo) t.start() print getattr(x, "st", 4) print x.st rurple-ng-0.5+16/experiments/multip000077500000000000000000000007561124731324500173300ustar00rootroot00000000000000#!/usr/bin/env python import multiprocessing import wx def more(label, i): label.SetValue("go around: " + str(i)) wx.CallLater(1000, more, label, i+1) class App(wx.App): def OnInit(self): frame = wx.Frame(None, title="This is a test") label = wx.TextCtrl(frame, wx.ID_ANY) more(label, 0) frame.Show(True) self.SetTopWindow(frame) return True def main(): app = App(0) app.MainLoop() if __name__ == "__main__": main() rurple-ng-0.5+16/experiments/scrolledwindow000077500000000000000000000056001124731324500210460ustar00rootroot00000000000000#!/usr/bin/env python import wx class MyWindow(wx.ScrolledWindow): def __init__(self, *args, **kw): wx.ScrolledWindow.__init__(self, *args, **kw) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnClick) self._radius = 10 self._dots = [(10,10), (100,100)] self.SetScrollbars(100, 100, 20, 20, 0, 0) self.SetBackgroundColour(wx.Colour(0, 0, 0)) def Offset(self): xm, ym = self.GetScrollPixelsPerUnit() tx, ty = self.ViewStart return -tx * xm, -ty * ym def OnPaint(self, e): dc = wx.PaintDC(self) #self.DoPrepareDC(dc) doesn't seem to work gc = wx.GraphicsContext.Create(dc) gc.Translate(*self.Offset()) gc.SetBrush(wx.Brush("cyan")) for dot in self._dots: path = gc.CreatePath() path.AddCircle(dot[0], dot[1], self._radius) gc.FillPath(path) def SRefresh(self, x, y, w, h): tx, ty = self.Offset() self.RefreshRect(wx.Rect(x + tx, y + ty, w, h)) def OnClick(self, e): tx, ty = self.Offset() x, y = e.GetX() - tx, e.GetY() - ty self._dots.append((x, y)) #self.Refresh() self.SRefresh(x - self._radius, y - self._radius, self._radius *2, self._radius *2) class MagicSash(wx.SplitterWindow): def __init__(self, *args, **kw): wx.SplitterWindow.__init__(self, *args, **kw) self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashDragged, self) self.Bind(wx.EVT_SIZE, self.OnSize, self) def OnSashDragged(self, e): print "Pos:", e.SashPosition def OnSize(self, e): print "Size:", e.Size e.Skip() class MyFrame(wx.Frame): def __init__(self, *args, **kw): wx.Frame.__init__(self, *args, **kw) filemenu= wx.Menu() self.Bind(wx.EVT_MENU, self.OnExit, filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")) menuBar = wx.MenuBar() menuBar.Append(filemenu,"&File") self.SetMenuBar(menuBar) self._vsash = MagicSash(self) vpanel = wx.Panel(self._vsash) hsash = wx.SplitterWindow(self._vsash) my = MyWindow(hsash) hpanel = wx.Panel(hsash) hsash.SplitHorizontally(my, hpanel) self._vsash.SplitVertically(vpanel, hsash) self._vsash.SetSashGravity(1.0) def adjust(self): w, h = self._vsash.GetSizeTuple() self._vsash.SetSashPosition(w - 200) def OnExit(self, e): self.Close(True) class MyApp(wx.App): def OnInit(self): print wx.DisplaySize() frame = MyFrame(None, title="This is a test", size=(500, 400)) frame.Show(True) self.SetTopWindow(frame) return True def main(): app = MyApp(0) app.MainLoop() if __name__ == "__main__": main() rurple-ng-0.5+16/experiments/toggleme000077500000000000000000000021601124731324500176100ustar00rootroot00000000000000#!/usr/bin/env python import wx class MyFrame(wx.Frame): def __init__(self, *args, **kw): wx.Frame.__init__(self, *args, **kw) self._toolbar = self.CreateToolBar() tsize = (24,24) self._toolbar.SetToolBitmapSize(tsize) self._undoTool = self._toolbar.AddRadioLabelTool(wx.ID_ANY, "Undo", wx.ArtProvider.GetBitmap(wx.ART_UNDO, wx.ART_TOOLBAR, tsize)) self._redoTool = self._toolbar.AddRadioLabelTool(wx.ID_ANY, "Redo", wx.ArtProvider.GetBitmap(wx.ART_REDO, wx.ART_TOOLBAR, tsize)) self._crossTool = self._toolbar.AddLabelTool(wx.ID_ANY, "Cross", wx.ArtProvider.GetBitmap(wx.ART_CROSS_MARK, wx.ART_TOOLBAR, tsize)) self.Bind(wx.EVT_TOOL, self.OnCross, self._crossTool) def OnCross(self, e): self._toolbar.ToggleTool(self._redoTool.Id, True) class MyApp(wx.App): def OnInit(self): frame = MyFrame(None, title="This is a test") frame.Show(True) self.SetTopWindow(frame) return True def main(): app = MyApp(0) app.MainLoop() if __name__ == "__main__": main() rurple-ng-0.5+16/experiments/tracefail000077500000000000000000000006071124731324500177430ustar00rootroot00000000000000#!/usr/bin/python import sys def tracefunc(frame, event, arg): #print frame, event, arg print event, frame.f_code.co_filename, frame.f_lineno if frame.f_lineno > 3: print "DIEEE" raise Exception() return tracefunc execme = """print 5 print 3 for i in range(4): print i print 8""" sys.settrace(tracefunc) exec execme sys.settrace(None) print "foo" rurple-ng-0.5+16/experiments/traceme000077500000000000000000000004741124731324500174330ustar00rootroot00000000000000#!/usr/bin/python import sys def tracefunc(frame, event, arg): #print frame, event, arg print event, frame.f_code.co_filename, frame.f_lineno return tracefunc execme = """print 5 print 3 for i in range(4): print i print 8""" sys.settrace(tracefunc) exec execme sys.settrace(None) print "foo" rurple-ng-0.5+16/experiments/twisted000077500000000000000000000020741124731324500174740ustar00rootroot00000000000000#!/usr/bin/python import sys from twisted.internet import threads, reactor, defer class ThreadedDebugger(object): def __init__(self, runme): self._runme = runme def _runInThread(self): try: sys.settrace(self._tracefunc) exec self._runme finally: sys.settrace(None) def go(self): reactor.callInThread(self._runInThread) def _tracefunc(self, frame, event, arg): if "" in frame.f_code.co_filename: threads.blockingCallFromThread(reactor, self._traceLine, frame.f_lineno) return self._tracefunc def _traceLine(self, lineno): print "On line %d: %s" %(lineno, self._runme.split("\n")[lineno-1]) d = defer.Deferred() reactor.callLater(0.5, d.callback, None) return d db = ThreadedDebugger("""print 5 print 3 for i in range(4): print i print 8""") reactor.callWhenRunning(db.go) def loopForever(): print "looping..." reactor.callLater(0.7, loopForever) #reactor.callWhenRunning(loopForever) reactor.run() rurple-ng-0.5+16/experiments/wxtwisted000077500000000000000000000015501124731324500200510ustar00rootroot00000000000000#!/usr/bin/env python TWIST = True if TWIST: import twisted.internet.wxreactor twisted.internet.wxreactor.install() from twisted.internet import reactor import wx class MyFrame(wx.Frame): def __init__(self, *args, **kw): wx.Frame.__init__(self, *args, **kw) self.Bind(wx.EVT_BUTTON, self.OnExit, wx.Button(self, wx.ID_ANY, "Exit")) wx.EVT_CLOSE(self, lambda evt: reactor.stop()) def OnExit(self, e): reactor.stop() #self.Close(True) class MyApp(wx.App): def OnInit(self): frame = MyFrame(None, title="This is a test") frame.Show(True) self.SetTopWindow(frame) return True def main(): app = MyApp(0) if TWIST: reactor.registerWxApp(app) reactor.run(0) else: app.MainLoop() if __name__ == "__main__": main() rurple-ng-0.5+16/manual.docbook000066400000000000000000000326521124731324500163440ustar00rootroot00000000000000
Rurple NG 0.5 PaulCrowley 2009Paul Crowley
About this release Rurple NG is a tool for teaching programming to those who have never programmed before. It is very closely based on an existing tool, RUR-PLE, but in a brand new implementation with various improvements. This program is not ready for you to learn programming from on your own This is an early release of this program and does not include a lesson plan. If you want to teach yourself programming on your own, please download and use RUR-PLE instead. If you already know how to program, and want to teach programming to others, this program might suit you. If you choose to adapt the RUR-PLE lesson plan, be sure to read . If you use this program, please let me know and provide whatever feedback you can: paul@lshift.net. Rurple NG home page
A brief guide to Rurple NG In the right hand window is a robot in a maze. Click on the window and you can move the robot forward with the "up" arrow key, or turn her left with the "left" arrow. If she bumps into a wall, you will see an error - but don't worry, she always recovers. In the left hand window is your program. A simple program can be no more than a sequence of commands like move() and turn_left() from the list below each on their own line, though all the power of the Python programming language is available to you. You can load and save programs using the "File" menu; the "Open sample..." menu item browses example programs shipped with Rurple NG. Click on the "Run" button to start your program running. You can change the speed at which it runs by moving the slider. Or, you can press "Pause" and then use the "Step" button to step through each line at your own pace. Pressing "Stop" stops the program running altogether; next time it starts it will start from the beginning. You can only edit your program when it is not running (ie when "Stop" is highlighted"). If your program goes wrong it will stop, an error will be shown, and the line on which it failed will be highlighted until you dismiss the error. The error and line number are also written to the log window underneath the maze. When your program is not running, you can change the maze. Click on where the walls go to add or remove walls. Or click in the middle of the squares to set the number of "stones" in that square. The robot can pick stones up, carry them, and put them down. You can load and save mazes using the "World" menu; the "Open sample..." menu item browses example mazes shipped with Rurple NG. The "Set stones..." menu entry allows you to change the number of stones the robot is carrying when your program starts. After running your program, you can press "Reset" to take the maze and robot back to the way they were when you first started it.
Rurple NG commands move() turn_left() front_is_clear() left_is_clear() right_is_clear() facing_north() pick_stone() put_stone() on_stone() got_stone() roll_dice() input_string() input_int() print()
Rurple NG and RUR-PLE Rurple NG clearly owes a tremendous debt to the marvellous program that inspired it. Here are some of the differences.
Incompatibilities between RUR-PLE and Rurple NG There is no turn_off() in Rurple NG; the robot stops when the program finishes. Rurple NG renames "beepers" to "stones", to make it clearer that they are inert. print() must be called with parentheses, just like all the other commands. Rurple NG currently lacks any support for multiple robots, or object-oriented robot commands The user program is interpreted with an implicit "from __future__ import division, print_function, unicode_literals" at the start. As a result, in Rurple NG evaluating "3 / 2" gives 1.5, not 1, and "3 // 2" evaluates to 1.
Advantages of Rurple NG over RUR-PLE Rurple NG was written mainly to remove confusing and distracting elements from the user interface, to make it more visually appealing and more familiar. However, it also has a far cleaner internal architecture which among other improvements makes better use of the machine's resources. RUR-PLE is four applications in one, separated by tabs. Rurple NG is just one of these applications; other programs can better do the jobs done by the other tabs. Instead of the browser tab, users are encouraged to use the browser they are most familiar with; this also allows them to have the browser and programming windows open side-by-side. And we anticipate that when users have reached the level of sophistication that they have outgrown what the robot and maze window can teach them, they should move on to a programming environment suitable for real work; this could be as simple as a favourite editor and text files, or a more sophisticated environment such as Eric, or Eclipse's Pydev. All functions in RUR-PLE are achieved with buttons in the toolbar. Rurple NG has a menu bar organised in the normal way. We anticipate that even the youngest users will likely have mastered the most usual parts of their OS's GUI before starting to learn programming, so Rurple NG tries to conform to the standard behaviour of a GUI application where possible. In addition, Rurple NG uses wxWidget's standard toolbar widget rather than re-implementing it using sashes, which provides a more attractive and standards-compliant user interface. Rurple NG's maze window is much less busy. All labels have been removed; the lessons supplied with RUR-PLE don't seem to make use either of the "streets/avenues" labelling or the numbers around the edge. And the grid marks the boundaries of the cells the robot can occupy; in the RUR-PLE interface the robot occupies the intersections of the grid lines, which seems less intuitive. The maze window is editable whenever the program is not running; there is no need to switch editing on and off. RUR-PLE has an edit mode during which an extra set of labels and grid lines is visible; I don't feel these add enough information to pay for the extra UI complexity. The robot is seen from above. In RUR-PLE the robot is seen in elevation even though the maze is seen in plan; in my experience users find thinking about which way the robot might be facing confusing enough without adding this extra step, even though the elevation view is more attractive with RUR-PLE's charming artwork. Rurple NG has no turn_off() command. The reasons for having such a command seem to stem from RUR-PLE's background with Karel the Robot; it may be a good fit in the teaching languages it originates with, but isn't very Pythonic. This means that a working Rurple NG program can be one line long. In a compromise between teaching Python 2 and Python 3, Rurple NG imports everything it can from __future__. This means that users can target their code at Python 2 and so make use of the vast range of libraries available, but they will learn habits that will serve them if they later switch to Python 3. Moving the slider to change the execution speed takes effect immediately even when the program is running in Rurple NG. This slider uses a logarithmic scale to make as much of the range as possible useful. Rurple NG uses the run/pause/stop buttons in the toolbar to indicate the current run state. Rurple NG indicates the line on which an error occurred until the dialog is dismissed, even when the error is a syntax error. In addition the error is written to the log window so the user can consult it while editing. Rurple NG automatically saves the program the user is currently working on at exit, and re-loads it on startup. It also saves a copy of the current world whenever the program is run, and re-loads it on startup or when the "reset" button is pressed. RUR-PLE provides a folding editor. I think this is a feature for more advanced programmers than RUR-PLE's target audience; folding part of a program by accident could be confusing, and the fold markers can be a visual distraction. I've also switched off the "dots" that indicate the presence of white space; my users found these dots confusing. What RUR-PLE calls "beepers", Rurple NG calls "stones". I found that some users expected beepers to be more "active", so I wanted to give them a more inert name. Rurple NG uses antialiased graphics to draw the maze window, among various other changes to give that window a cleaner and less busy look. Rurple NG does not have a sash for displaying the data structure representing the current state of the maze. Again, I don't believe the UI complexity that this feature adds pays for itself in teaching. In its place I may add a tab to the log window showing the current local variables for the running program, which would allow the user the more useful function of tracking data structures of their own making. As well as these user-visible improvements, there are various improvements under the hood: Rurple NG runs the user program in a separate thread. RUR-PLE runs the program in the GUI thread, and explicitly calls wx.Yield() when paused, polling to determine when to continue running. This causes unnecessary CPU load. A future revision of Rurple NG may even run user code in a separate process. RUR-PLE is 4850 lines of Python; Rurple NG is 1651 lines. This is in part because Rurple NG deliberately omits many of RUR-PLE's features, and in part because of features like multiple robot support that have yet to be added to Rurple NG, but is also because of much more careful factoring of code to avoid repetition. RUR-PLE was one of the first sizeable Python programs written by its author; Rurple NG is by an experienced Python programmer. Rurple NG's code more cleanly separates the work of its modules. In particular, Rurple NG is designed to allow new and different "worlds" to be dropped in as modules, so that user programs could do more than explore the maze that it currently provides. Under Windows, Rurple NG uses WiX rather than InnoSetup to build its installer. WiX is free software and builds an MSI rather than an EXE, which makes the install and uninstall process much more robust.
Advantages of RUR-PLE over Rurple NG Though many features in RUR-PLE are missing from Rurple NG on purpose, there are still a few features in RUR-PLE that are desirable and have not yet been added to Rurple NG. Most importantly, RUR-PLE has a complete manual and lesson plan. RUR-PLE supports four natural languages; Rurple NG doesn't currently use gettext. RUR-PLE has support for multiple robots and an object-oriented interface for acting on them. RUR-PLE is a more mature and well-tested program.
rurple-ng-0.5+16/rurple.wxs000066400000000000000000000604561124731324500156040ustar00rootroot00000000000000 1 1 1 1 1 1 1 NOT Installed Installed 1 1 1 1 rurple-ng-0.5+16/rurple/000077500000000000000000000000001124731324500150265ustar00rootroot00000000000000rurple-ng-0.5+16/rurple/__init__.py000066400000000000000000000000001124731324500171250ustar00rootroot00000000000000rurple-ng-0.5+16/rurple/cpu.py000066400000000000000000000112171124731324500161710ustar00rootroot00000000000000from __future__ import division, print_function, unicode_literals, with_statement import sys import time import threading import Queue import wx class ThreadStopException(BaseException): pass class TraceThread(threading.Thread): def __init__(self, cpu, world, program): threading.Thread.__init__(self) self._cpu = cpu self._world = world self._globals = self._world.getGlobals(self) self._program = program self._stopped = False self._traceProxy = self.threadAwareProxyFunction(self._cpu.trace) def _done(self): if not self._stopped: self._cpu.done() def _failed(self, e): if not self._stopped: self._cpu.failed(e) def run(self): try: sys.settrace(self._tracefunc) try: exec self._program in self._globals wx.CallAfter(self._done) except BaseException, e: wx.CallAfter(self._failed, e) finally: # not really necessary - thread's done anyway sys.settrace(None) def stop(self): self._stopped = True def threadAwareProxyFunction(self, f): def guardF(rcb, *a, **kw): if self._stopped: rcb((None, ThreadStopException())) else: f(self, rcb, *a, **kw) def callF(*a, **kw): q = Queue.Queue() wx.CallAfter(guardF, q.put, *a, **kw) res, exc = q.get() if exc is not None: raise exc return res return callF def proxyFunction(self, f): def callBlocking(t, rcb, *a, **kw): try: rcb((f(*a, **kw), None)) except Exception, e: rcb((None, e)) return self.threadAwareProxyFunction(callBlocking) def _tracefunc(self, frame, event, arg): # FIXME: shame to stop only on the lines in string if stopped if "" in frame.f_code.co_filename: if event == "line": self._traceProxy(frame) return self._tracefunc STOP = 1 PAUSE = 2 RUN = 3 class CPU(object): def __init__(self, ui): self._ui = ui self._lineTime = 1000 self._clear() def _clear(self): self._state = STOP self._timer = None self._thread = None self._rcb = None self._frame = None def _start(self): world = self._ui.world self._ui.starting() self._thread = TraceThread(self, world, self._ui.program) self._lastTime = time.time() self._thread.start() def _release(self): r = self._rcb if r is not None: self._rcb = None self._timer = None self._frame = None self._lastTime = time.time() r((None, None)) def _waitTime(self): return max(1, int( self._lineTime - 1000*(time.time() - self._lastTime))) def _stopTimer(self): if self._timer is not None: self._timer.Stop() self._timer = None # ----- Thread methods ------ def trace(self, t, rcb, frame): if self._state == STOP: return # FIXME: assert out self._frame = frame self._rcb = rcb if self._state == RUN: self._timer = wx.CallLater(self._waitTime(), self._release) self._ui.trace(frame) def done(self): self._clear() self._ui.done() def failed(self, e): self._clear() self._ui.failed(e) # ----- UI methods ----- @property def state(self): return self._state def run(self): if self._state == STOP: self._state = RUN self._ui.running() self._start() elif self._state == PAUSE: self._state = RUN self._ui.running() self._release() def pause(self): if self._state == STOP: self._state = PAUSE self._ui.pausing() self._start() elif self._state == RUN: self._state = PAUSE self._ui.pausing() self._stopTimer() def stop(self): if self._state == STOP: return self._stopTimer() if self._thread: self._thread.stop() self._clear() self._ui.stopped() def step(self): self.pause() self._release() def setLineTime(self, t): self._lineTime = t if self._timer is not None: self._timer.Restart(self._waitTime()) __all__ = [STOP, PAUSE, RUN, CPU] rurple-ng-0.5+16/rurple/listctrl.py000066400000000000000000000024621124731324500172440ustar00rootroot00000000000000from __future__ import division, print_function, unicode_literals import wx class ListCtrl(wx.ListCtrl): def __init__(self, *a, **kw): wx.ListCtrl.__init__(self, *a, **kw) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_LIST_COL_END_DRAG, self.OnSize) def SetItems(self, items): for ix, item in enumerate(sorted(items)): n = item[0] while True: if ix >= self.ItemCount: self.InsertStringItem(ix, n) break v = self.GetItemText(ix) if v < n: self.DeleteItem(ix) elif v == n: break else: self.InsertStringItem(ix, n) break for c, v in enumerate(item[1:]): self.SetStringItem(ix, c+1, v) ix = len(items) while ix < self.ItemCount: self.DeleteItem(ix) def OnSize(self, e): e.Skip() w, h = self.ClientSize cc = self.ColumnCount if cc < 1: return for i in range(cc -1): w -= self.GetColumnWidth(i) # UGLY KLUDGE: try to leave room for the scrollbar w -= 20 self.SetColumnWidth(cc -1, max(w, 1)) rurple-ng-0.5+16/rurple/share.py000066400000000000000000000012331124731324500165010ustar00rootroot00000000000000from __future__ import division, print_function, unicode_literals, with_statement import sys import os import os.path import wx def _toPath(n): return os.path.abspath(unicode(n, sys.getfilesystemencoding())) def _topPath(): if hasattr(sys, "frozen"): return os.path.dirname(_toPath(sys.executable)) else: return os.path.dirname(os.path.dirname(_toPath(__file__))) _sharePath = os.path.join(_topPath(), "share") def path(*a): return os.path.join(_sharePath, *a) def toBitmap(name): return wx.Image(path('images', '%s.png' % name), wx.BITMAP_TYPE_PNG).ConvertToBitmap() __all__ = [path, toBitmap] rurple-ng-0.5+16/rurple/textctrl.py000066400000000000000000000126031124731324500172530ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import division, print_function, unicode_literals, with_statement import re import keyword import wx from wx import stc if wx.Platform == '__WXMSW__': faces = { 'mono' : 'Courier New', 'helv' : 'Arial', 'size' : 11, 'size2': 8, } elif wx.Platform == '__WXMAC__': faces = { 'mono' : 'Courier New', 'helv' : 'Arial', 'size' : 16, 'size2': 10, } else: faces = { 'mono' : 'Courier', 'helv' : 'Helvetica', 'size' : 11, 'size2': 9, } class PythonEditor(stc.StyledTextCtrl): MARK_RUNNING = 7 _styleSpecs = [ (stc.STC_P_DEFAULT, "fore:#000000,face:%(mono)s,size:%(size)d"), (stc.STC_P_COMMENTLINE, "fore:#009900,face:%(mono)s,size:%(size)d"), (stc.STC_P_NUMBER, "fore:#FF0000,bold,size:%(size)d"), (stc.STC_P_STRING, "fore:#660066,face:%(mono)s,size:%(size)d"), (stc.STC_P_CHARACTER, # Single quoted string "fore:#660066,face:%(mono)s,size:%(size)d"), (stc.STC_P_WORD, # Keyword "fore:#336699,bold,face:%(mono)s,size:%(size)d"), (stc.STC_P_TRIPLE, # Triple quotes "fore:#660066,size:%(size)d"), (stc.STC_P_TRIPLEDOUBLE, # Triple double quotes "fore:#660066,size:%(size)d"), (stc.STC_P_CLASSNAME, # Class name definition "fore:#000099,bold,underline,face:%(mono)s,size:%(size)d"), (stc.STC_P_DEFNAME, # Function or method name definition "fore:#3333ff,bold,face:%(mono)s,size:%(size)d"), (stc.STC_P_OPERATOR, "bold,size:%(size)d"), (stc.STC_P_IDENTIFIER, "fore:#000000,face:%(mono)s,size:%(size)d"), (stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d"), (stc.STC_P_STRINGEOL, # End of line where string is not closed "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d"), (stc.STC_STYLE_INDENTGUIDE, "fore:#333333"), (stc.STC_STYLE_LINENUMBER, "back:#99AACC,face:%(helv)s,size:%(size2)d"), ] def __init__(self, *a, **kw): stc.StyledTextCtrl.__init__(self, *a, **kw) self.MarkerDefine(self.MARK_RUNNING, stc.STC_MARK_BACKGROUND, 'white', 'wheat') self.UseHorizontalScrollBar = False self.Margins = (2, 2) self.SetMarginType(1, stc.STC_MARGIN_NUMBER) # Reasonable (?) value for 4 digits using a small mono font (33 pixels) self.SetMarginWidth(1, 33) self.SetProperty("fold", "1") self.SetProperty("tab.timmy.whinge.level", "1") for styleNum, spec in self._styleSpecs: self.StyleSetSpec(styleNum, spec % faces), # Python styles ---------------------------------------- self.SetLexer(stc.STC_LEX_PYTHON) kl = set(keyword.kwlist) | set(['None', 'True', 'False']) kl.remove('print') self.SetKeyWords(0, " ".join(kl)) self.Indent = 4 self.IndentationGuides = True self.BackSpaceUnIndents = True self.TabIndents = True self.TabWidth = 4 self.UseTabs = False self._modifyHook = None self._modified = False self._mark = None self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed) self.Bind(wx.stc.EVT_STC_SAVEPOINTLEFT, self.OnSavePointLeft) self.Bind(wx.stc.EVT_STC_SAVEPOINTREACHED, self.OnSavePointReached) def OnKeyPressed(self, event): key = event.GetKeyCode() # Indentation if key == wx.WXK_RETURN: ind = "\n" m = re.match(" +", self.CurLine[0]) if m: ind += m.group(0) if chr(self.GetCharAt(self.GetCurrentPos()-1)) == ":": ind += " " self.AddText(ind) else: event.Skip(True) def OnSavePointLeft(self, e): self.modified = True def OnSavePointReached(self, e): self.modified = False def clearModified(self): self.SetSavePoint() self.modified = False @property def modified(self): return self._modified @modified.setter def modified(self, v): if self._modified != v: self._modified = v if self._modifyHook is not None: self._modifyHook.update() # any reason this isn't a property? def modifyHook(self,opener): self._modifyHook = opener @property def mark(self): return self._mark @mark.setter def mark(self, line): if self._mark is not None: self.MarkerDelete(self._mark -1, self.MARK_RUNNING) self._mark = line if self._mark is not None: self.MarkerAdd(self._mark -1, self.MARK_RUNNING) class LogWindow(wx.stc.StyledTextCtrl): def __init__(self, *a, **kw): wx.stc.StyledTextCtrl.__init__(self, *a, **kw) self.ReadOnly = True self.UseHorizontalScrollBar = False self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces) def write(self, s): self.ReadOnly = False self.AddText(s) self.ReadOnly = True self.EnsureCaretVisible() def clear(self): self.ReadOnly = False self.SetText("") self.ReadOnly = True rurple-ng-0.5+16/rurple/ui.py000066400000000000000000000471441124731324500160270ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import division, print_function, unicode_literals, with_statement import types import math import os import os.path import codecs import json import wx import wx.lib.scrolledpanel import wx.lib.wordwrap from rurple import share, cpu, world, textctrl, listctrl import rurple.worlds.maze class LogScale(object): def __init__(self, ticks, lo, hi): self._lo = math.log(lo) self._r = (math.log(hi) - self._lo) / ticks def toTicks(self, x): return (math.log(x) - self._lo) / self._r def fromTicks(self, x): return math.exp(x * self._r + self._lo) # This class is currently a very close friend of ui, # might be desirable to change that class Openable(object): def __init__(self, ui): self._ui = ui self._path = None self._canSave = False @property def _wildcard(self): return "%s (*.%s)|*.%s" % ( self._typetext, self._suffix, self._suffix) @property def _dotfile(self): return os.path.join(self._ui._dotPath, "%s.%s"% (self._type, self._suffix)) def _suffixPath(self, path): s = "." + self._suffix if path.endswith(s): return path return path + s def _tildeSave(self, path): if os.path.exists(path): t = path + "~" if os.path.exists(t): os.remove(t) os.rename(path, t) self._tildeSave(path) self._save(path) def opendot_again(self): if not self._openGuard(): return self.opendot_init() def opendot_init(self): if os.path.isfile(self._dotfile): self._open(self._dotfile) else: self._blankStart() self.update() def savedot(self): self._tildeSave(self._dotfile) def _openSample(self, name): self._open(share.path("%ss" % self._type, "%s.%s" % (name, self._suffix))) # This is annoying - it should just STOP. # But only on success... def _openGuard(self): if self._ui._cpu.state == cpu.STOP: return True self._ui._cpu.pause() wx.MessageDialog(self._ui, caption="Program running", message = "Please press STOP before doing that\nCannot modify %s while program is running" % self._type, style=wx.ICON_ERROR | wx.OK).ShowModal() return False def _modGuard(self): if not self._modified(): return True code = wx.MessageDialog(self._ui, caption="Save %s first?" % self._type, message = "Save your changes to the %s before replacing it?" % self._type, style=wx.ICON_EXCLAMATION | wx.YES_NO | wx.CANCEL).ShowModal() if code == wx.ID_CANCEL: return False elif code == wx.ID_NO: return True else: return self.OnSave(None) def _exception(self, t, e): wx.MessageDialog(self._ui, caption="%s failed" % t.capitalize(), message = str(e), style=wx.ICON_ERROR | wx.OK).ShowModal() def update(self): if self._modified(): prefix = "*" else: prefix = "" if self._path is not None: text = "%s%s: %s" % (prefix, self._type.capitalize(), os.path.basename(self._path)) else: text = "%sNo %s open" % (prefix, self._type) #ugly! self._ui._statusBar.SetStatusText(text, self._statusPos) def OnNew(self, e): if not self._openGuard() or not self._modGuard(): return if self._new(): self._path = None self._clearModified() def OnOpen(self, e): if not self._openGuard() or not self._modGuard(): return dlg = wx.FileDialog(self._ui, message="Open %s..." % self._type, wildcard=self._wildcard, style = wx.OPEN | wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: return path = self._suffixPath(dlg.GetPath()) dlg.Destroy() try: self._open(path) except Exception, e: self._exception("open", e) else: self._path = path self._canSave = True self._clearModified() def OnOpenSample(self, e): if not self._openGuard() or not self._modGuard(): return dlg = wx.FileDialog(self._ui, message="Open sample %s..." % self._type, wildcard=self._wildcard, defaultDir = share.path(self._type + "s"), style = wx.OPEN) if dlg.ShowModal() != wx.ID_OK: return path = self._suffixPath(dlg.GetPath()) dlg.Destroy() try: self._open(path) except Exception, e: self._exception("open", e) else: self._path = path self._canSave = False self._clearModified() def OnSave(self, e): if self._canSave: self._tildeSave(self._path) self._clearModified() return True else: return self.OnSaveAs(e) def OnSaveAs(self, e): dlg = wx.FileDialog(self._ui, message="Save %s as..." % self._type, wildcard=self._wildcard, style = wx.SAVE | wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: return False path = self._suffixPath(dlg.GetPath()) dlg.Destroy() if os.path.exists(path): d = wx.MessageDialog(self._ui, caption="Overwrite file?", message = "There already is a %s of that name. Replace it with your %s?" % (self._type, self._type), style=wx.ICON_QUESTION | wx.OK | wx.CANCEL) if d.ShowModal() != wx.ID_OK: return False try: self._tildeSave(path) except Exception, e: self._exception("save", e) else: self._path = path self._canSave = True self._clearModified() return True class ProgramOpen(Openable): _type = "program" _typetext = "RUR programs" _suffix = "rur" _statusPos = 0 def _new(self): self._ui._editor.Text = "" return True def _modified(self): return self._ui._editor.modified def _clearModified(self): self._ui._editor.clearModified() self.update() def _open(self, fn): with codecs.open(fn, encoding="utf-8") as f: p = f.read() self._ui._editor.Text = p def _save(self, fn): with codecs.open(fn, "w", encoding="utf-8") as f: f.write(self._ui.program) def _blankStart(self): self._ui._editor.Text = "" class WorldOpen(Openable): _type = "world" _typetext = "Worlds" _suffix = "wld" _statusPos = 1 def _new(self): d = rurple.worlds.maze.NewDialog(self._ui) if d.ShowModal() != wx.ID_OK: return False self._ui.world = d.makeWorld(self._ui) return True def _modified(self): return self._ui.world.modified def _clearModified(self): self._ui._world.modified = False self.update() def _open(self, fn): with open(fn) as f: w = json.load(f) w = rurple.worlds.maze.World(self._ui, w) w.modifyHook(self) self._ui.world = w def _save(self, fn): with open(fn, "w") as f: json.dump(self._ui.world.staterep, f, indent=4, sort_keys = True) def _blankStart(self): self._openSample("blank") class RurFrame(wx.Frame): def __init__(self, *args, **kw): wx.Frame.__init__(self, *args, **kw) # FIXME: not right for Windows or MacOS X self._dotPath = os.path.expanduser("~/.rurple") if not os.path.isdir(self._dotPath): os.mkdir(self._dotPath) self._programFile = None self._worldFile = None self._cpu = cpu.CPU(self) self._vsash = wx.SplitterWindow(self) self._editor = textctrl.PythonEditor(self._vsash) self._editor.CodePage = 65001 self._hsash = wx.SplitterWindow(self._vsash) self._worldParent = wx.lib.scrolledpanel.ScrolledPanel(self._hsash) self._worldWindow = None self._vsash.SplitVertically(self._editor, self._hsash) self._notebook = wx.Notebook(self._hsash) self._logWindow = textctrl.LogWindow(self._notebook) self._notebook.AddPage(self._logWindow, "Log") self._funcs = listctrl.ListCtrl(self._notebook, style = wx.LC_REPORT|wx.LC_HRULES) self._funcs.InsertColumn(0, "Function") self._notebook.AddPage(self._funcs, "Functions") self._vars = listctrl.ListCtrl(self._notebook, style = wx.LC_REPORT|wx.LC_HRULES) self._vars.InsertColumn(0, "Variable") self._vars.InsertColumn(1, "Value") self._notebook.AddPage(self._vars, "Variables") self._hsash.SplitHorizontally(self._worldParent, self._notebook) self._worldParent.SetupScrolling() self._statusBar = self.CreateStatusBar() self._statusBar.SetFieldsCount(3) self._programO = ProgramOpen(self) self._worldO = WorldOpen(self) self._programO.opendot_init() # ugly! self._programO._clearModified() self._editor.modifyHook(self._programO) menuBar = wx.MenuBar() filemenu = wx.Menu() self.Bind(wx.EVT_MENU, self._programO.OnNew, filemenu.Append(wx.ID_NEW, "&New", "Start a new program")) self.Bind(wx.EVT_MENU, self._programO.OnOpen, filemenu.Append(wx.ID_OPEN, "&Open...", "Open a program")) self.Bind(wx.EVT_MENU, self._programO.OnOpenSample, filemenu.Append(wx.ID_ANY, "Open sa&mple...", "Open a sample program")) self.Bind(wx.EVT_MENU, self._programO.OnSave, filemenu.Append(wx.ID_SAVE,"&Save", "Save the current program")) self.Bind(wx.EVT_MENU, self._programO.OnSaveAs, filemenu.Append(wx.ID_SAVEAS,"Save &As...", "Save the current program with a different filename")) filemenu.AppendSeparator() self.Bind(wx.EVT_MENU, self.OnExit, filemenu.Append(wx.ID_EXIT,"E&xit", "Close Rurple")) menuBar.Append(filemenu,"&File") runmenu = wx.Menu() self.Bind(wx.EVT_MENU, self.OnRun, runmenu.Append(wx.ID_ANY,"&Run\tF8", "Start the program running")) self.Bind(wx.EVT_MENU, self.OnPause, runmenu.Append(wx.ID_ANY,"&Pause", "Pause the program")) self.Bind(wx.EVT_MENU, self.OnStop, runmenu.Append(wx.ID_ANY,"S&top\tCtrl+F2", "Stop the program")) self.Bind(wx.EVT_MENU, self.OnStep, runmenu.Append(wx.ID_ANY,"&Step\tF5", "Step one line of the program")) menuBar.Append(runmenu,"&Run") self._worldMenu = wx.Menu() self.Bind(wx.EVT_MENU, self.OnWorldReset, self._worldMenu.Append(wx.ID_ANY,"&Reset\tCtrl+R", "Reset world to before program was run")) self._worldMenu.AppendSeparator() self.Bind(wx.EVT_MENU, self._worldO.OnNew, self._worldMenu.Append(wx.ID_ANY,"&New...", "Start a new world")) self.Bind(wx.EVT_MENU, self._worldO.OnOpen, self._worldMenu.Append(wx.ID_ANY,"&Open...", "Open a world")) self.Bind(wx.EVT_MENU, self._worldO.OnOpenSample, self._worldMenu.Append(wx.ID_ANY, "Open sa&mple...", "Open a sample world")) self.Bind(wx.EVT_MENU, self._worldO.OnSave, self._worldMenu.Append(wx.ID_ANY,"&Save", "Save the current world")) self.Bind(wx.EVT_MENU, self._worldO.OnSaveAs, self._worldMenu.Append(wx.ID_ANY,"Save &As...", "Save the current world with a different filename")) self._worldMenu.AppendSeparator() self._worldMenuItems = self._worldMenu.MenuItemCount menuBar.Append(self._worldMenu,"&World") helpmenu = wx.Menu() self.Bind(wx.EVT_MENU, self.OnAbout, helpmenu.Append(wx.ID_ABOUT, "&About...\tF1", "Information about Rurple")) menuBar.Append(helpmenu,"&Help") self.SetMenuBar(menuBar) self._toolbar = self.CreateToolBar() tsize = (24,24) self._toolbar.SetToolBitmapSize(tsize) self.Bind(wx.EVT_TOOL, self.OnWorldReset, self._toolbar.AddLabelTool(wx.ID_ANY, "Reset", share.toBitmap('reset_world'), shortHelp = "Reset world (Ctrl+R)", longHelp = "Put world back the way it was when your program started")) self._toolbar.AddSeparator() self._runTool = self._toolbar.AddRadioLabelTool(wx.ID_ANY, "Run", share.toBitmap('run'), shortHelp="Run program (F8)", longHelp="Start your program running") self.Bind(wx.EVT_TOOL, self.OnRun, self._runTool) self._pauseTool = self._toolbar.AddRadioLabelTool(wx.ID_ANY, "Pause", share.toBitmap('pause'), shortHelp="Pause program", longHelp="Pause your program") self.Bind(wx.EVT_TOOL, self.OnPause, self._pauseTool) self._stopTool = self._toolbar.AddRadioLabelTool(wx.ID_ANY, "Stop", share.toBitmap('stop'), shortHelp="Stop program (Ctrl+F2)", longHelp="Finish your program early") self.Bind(wx.EVT_TOOL, self.OnStop, self._stopTool) self.Bind(wx.EVT_TOOL, self.OnStep, self._toolbar.AddLabelTool(wx.ID_ANY, "step", share.toBitmap('step'), shortHelp="Step program (F5)", longHelp="Execute only the current line of your program")) self._toolbar.ToggleTool(self._stopTool.Id, True) self._toolbar.AddSeparator() self._toolbar.AddControl( wx.StaticText(self._toolbar, label="Speed:")) self._slideScale = LogScale(100, 2000, 5) self._slider = wx.Slider(self._toolbar, size=(250,-1), value=int(0.5 + self._slideScale.toTicks(300))) self.Bind(wx.EVT_SLIDER, self.OnSlide, self._slider) self.OnSlide(None) self._toolbar.AddControl(self._slider) self._toolbar.Realize() self._worldO.opendot_init() w, h = self.Size dw, dh = self._worldWindow.size() self._vsash.SashGravity = 1 self._hsash.SashPosition = min(h, dh + 20) self._vsash.SashPosition = max(0, w - (dw + 30)) self.Bind(wx.EVT_CLOSE, self.OnClose, self) def _reset(self): self._worldO.opendot_again() def OnClose(self, e): self._programO.savedot() if self._cpu.state == cpu.STOP: self._worldO.savedot() self.Destroy() def OnExit(self, e): self.Close(True) def OnRun(self, e): self._cpu.run() def OnPause(self, e): self._cpu.pause() def OnStop(self, e): self._cpu.stop() def OnStep(self, e): self._cpu.step() def OnWorldReset(self, e): self._reset() def OnAbout(self, e): info = wx.AboutDialogInfo() info.Name = "Rurple NG" info.Version = "0.5" info.Copyright = "Copyright 2009 André Roberge and Paul Crowley" info.Description = wx.lib.wordwrap.wordwrap( "A friendly learning environment for beginners to programming." "To get started, have a look at the manual", 350, wx.ClientDC(self)) info.WebSite = (share.path("html", "index.html"), "Rurple NG manual") info.Developers = ["André Roberge", "Paul Crowley"] with open(share.path("COPYING.txt")) as f: #info.License = wx.lib.wordwrap.wordwrap( # f.read(), 500, wx.ClientDC(self)) info.License = f.read().replace("\f", "") wx.AboutBox(info) def OnSlide(self, e): self._cpu.setLineTime(self._slideScale.fromTicks(self._slider.Value)) # read by world @property def log(self): return self._logWindow # read by cpu @property def program(self): return self._editor.Text.replace("\r\n", "\n") # read by cpu @property def world(self): return self._world @world.setter def world(self, world): if self._worldWindow is not None: self._worldWindow.Destroy() self._worldWindow = None self._world = world self._worldWindow = self._world.makeWindow(self._worldParent) w, h = self._worldWindow.BestSize self._worldParent.SetScrollbars(1, 1, w, h, 0, 0) while self._worldMenuItems < self._worldMenu.MenuItemCount: mi = self._worldMenu.FindItemByPosition(self._worldMenuItems) self._worldMenu.Delete(mi.Id) for b, e in self._world.menu(self._worldMenu): self.Bind(wx.EVT_MENU, b, e) #sps.SetSizeHints(sp) # called by cpu def trace(self, frame): self._editor.mark = frame.f_lineno d = dict(frame.f_globals) d.update(frame.f_locals) varpairs = [] funcs = [] for k, v in sorted(d.iteritems()): if k.startswith("__"): continue if type(v) == types.FunctionType: funcs.append((k,)) else: varpairs.append((k, repr(v))) self._vars.SetItems(varpairs) self._funcs.SetItems(funcs) # called by cpu def starting(self): self._world.editable = False self._editor.ReadOnly = True self._programO.savedot() self._worldO.savedot() self._logWindow.clear() self._world.runStart() # called by cpu def running(self): self._toolbar.ToggleTool(self._runTool.Id, True) # called by cpu def pausing(self): self._toolbar.ToggleTool(self._pauseTool.Id, True) # called by cpu def stopped(self): self._toolbar.ToggleTool(self._stopTool.Id, True) self._editor.mark = None self._world.editable = True self._editor.ReadOnly = False # called by cpu def done(self): d = wx.MessageDialog(self, message="Your program finished", caption = "Program finished", style=wx.ICON_INFORMATION | wx.OK) d.ShowModal() self.stopped() # called by cpu def failed(self, e): if isinstance(e, world.WorldException): print("Exception on line %s:" % self._editor.mark, e, file=self._logWindow) self._world.handleException(self, e) else: # exceptions are where isinstance is allowed if (isinstance(e, SyntaxError) and self._editor.mark is None and e.filename == ""): self._editor.mark = e.lineno print("Exception on line %s:" % self._editor.mark, e, file=self._logWindow) d = wx.MessageDialog(self, message=str(e), caption = "Error running your program", style=wx.ICON_EXCLAMATION | wx.OK) d.ShowModal() self.stopped() # called by world def setWorldStatus(self, s): self._statusBar.SetStatusText(s, 2) class App(wx.App): def OnInit(self): w, h = wx.DisplaySize() frame = RurFrame(None, title="Rurple", size=(int(w*0.85), int(h*0.85))) frame.Show(True) self.SetTopWindow(frame) return True def main(): app = App(0) app.MainLoop() rurple-ng-0.5+16/rurple/world.py000066400000000000000000000001761124731324500165330ustar00rootroot00000000000000from __future__ import division, print_function, unicode_literals, with_statement class WorldException(Exception): pass rurple-ng-0.5+16/rurple/worlds/000077500000000000000000000000001124731324500163405ustar00rootroot00000000000000rurple-ng-0.5+16/rurple/worlds/__init__.py000066400000000000000000000000001124731324500204370ustar00rootroot00000000000000rurple-ng-0.5+16/rurple/worlds/maze.py000066400000000000000000000523001124731324500176460ustar00rootroot00000000000000from __future__ import division, print_function, unicode_literals, with_statement import math import random import wx import rurple.world, rurple.share class MazeException(rurple.world.WorldException): def __init__(self, message): rurple.world.WorldException.__init__(self, message) self._text = message @property def title(self): return "Something didn't work" @property def text(self): return self._text class Robot(object): def __init__(self, maze, state): self._maze = maze self._name = state['name'] self._x = state['x'] self._y = state['y'] self._dir = state['dir'] self._stones = state['stones'] self.runStart() self._maze.addRobot(self) self._maze.repaintSquares(self._x, self._y, 1, 1) def runStart(self): self._trail = [(self._x, self._y, self._dir)] def move(self): if not self.front_is_clear(): raise MazeException("Hit a wall") if self._dir == 0: self._x += 1 self._trail.append((self._x, self._y, self._dir)) self._maze.repaintSquares(self._x -1, self._y, 2, 1) elif self._dir == 1: self._y += 1 self._trail.append((self._x, self._y, self._dir)) self._maze.repaintSquares(self._x, self._y -1, 1, 2) elif self._dir == 2: self._x -= 1 self._trail.append((self._x, self._y, self._dir)) self._maze.repaintSquares(self._x, self._y, 2, 1) else: self._y -= 1 self._trail.append((self._x, self._y, self._dir)) self._maze.repaintSquares(self._x, self._y, 1, 2) self._maze.setModified() def turn_left(self): self._dir += 1 self._dir %= 4 self._trail.append((self._x, self._y, self._dir)) self._maze.repaintSquares(self._x, self._y, 1, 1) self._maze.setModified() def pick_stone(self): self._maze.pickStone(self._x, self._y) self._stones += 1 self._maze.statusChanged() def put_stone(self): if self._stones == 0: raise MazeException("I don't have any stones") self._stones -= 1 self._maze.putStone(self._x, self._y) self._maze.statusChanged() def on_stone(self): return self._maze.countStones(self._x, self._y) != 0 def got_stone(self): return self._stones != 0 def front_is_clear(self): return self._maze.isPassable(self._x, self._y, self._dir) def left_is_clear(self): return self._maze.isPassable(self._x, self._y, (self._dir +1) % 4) def right_is_clear(self): return self._maze.isPassable(self._x, self._y, (self._dir -1) % 4) def facing_north(self): return self._dir == 1 @property def name(self): return self._name @property def x(self): return self._x @property def y(self): return self._y @property def dir(self): return self._dir @property def stones(self): return self._stones @stones.setter def stones(self, x): assert x >= 0 assert x == int(x) self._stones = int(x) self._maze.statusChanged() @property def trail(self): return self._trail @property def staterep(self): return { "name": self._name, "x": self._x, "y": self._y, "dir": self._dir, "stones": self._stones, } class Maze(object): def __init__(self, world, state): self._world = world self._width = state['width'] self._height = state['height'] self._walls = ( set((x, 0, 'h') for x in range(self._width)) | set((x, self._height, 'h') for x in range(self._width)) | set((0, y, 'v') for y in range(self._height)) | set((self._width, y, 'v') for y in range(self._height)) | set( tuple(w) for w in state['walls'] if self.isInterior(*w))) self._stones = dict( (tuple(k), v) for k, v in state['stones']) self._robots = {} for r in state['robots']: Robot(self, r) def isInterior(self, x, y, d): if x >= self._width or y >= self._height: return False if d == 'h': return x >= 0 and y > 0 elif d == 'v': return x > 0 and y >= 0 def isPassable(self, x, y, d): if d == 0: return (x +1, y, 'v') not in self._walls elif d == 1: return (x, y +1, 'h') not in self._walls elif d == 2: return (x, y, 'v') not in self._walls else: return (x, y, 'h') not in self._walls def toggleWall(self, x, y, d): if not self.isInterior(x, y, d): return self._walls ^= set([(x, y, d)]) if d == 'h': self.repaintSquares(x-0.5, y-0.5, 2, 1) else: self.repaintSquares(x-0.5, y-0.5, 1, 2) self.setModified() def addRobot(self, r): n = r.name if n in self._robots: raise MazeException("Already got a robot called %s" % n) self._robots[n] = r self._defaultRobot = r self.statusChanged() def pickStone(self, x, y): b = self.countStones(x, y) if b == 0: raise MazeException("I'm not next to a stone") b -= 1 self.setStones(x, y, b) def putStone(self, x, y): self.setStones(x, y, 1 + self.countStones(x, y)) def setStones(self, x, y, i): assert i >= 0 if i == 0: if (x, y) in self._stones: del self._stones[(x, y)] else: self._stones[(x, y)] = i self.repaintSquares(x, y, 1, 1) def countStones(self, x, y): return self._stones.get((x, y), 0) @property def width(self): return self._width @property def height(self): return self._height @property def walls(self): return self._walls @property def robots(self): return self._robots @property def stones(self): return self._stones @property def defaultRobot(self): return self._defaultRobot @property def staterep(self): p = "rurple.worlds." n = __name__ if not n.startswith(p): raise Exception("Module in wrong namespace, can't save") return { "world": n[len(p):], "width": self._width, "height": self._height, "walls": list(w for w in self._walls if self.isInterior(*w)), "stones": [(k, v) for k, v in self._stones.iteritems()], "robots": [v.staterep for v in self._robots.itervalues()], } def runStart(self): for r in self._robots.itervalues(): r.runStart() # after deleting ink trails, redraw all self.repaintSquares(0, 0, self._width, self._height) def repaintSquares(self, x, y, w, h): self._world.repaintSquares(x, y, w, h) def setModified(self): self._world.modified = True def statusChanged(self): self._world.statusChanged() self.setModified() class MazeWindow(wx.PyControl): def __init__(self, *args, **kw): self._world = kw['world'] del kw['world'] wx.Window.__init__(self, *args, **kw) self._spacing = 20 self._offset = 20 self.SetBestSize(self.size()) wx.EVT_PAINT(self, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnClick, self) self.Bind(wx.EVT_CHAR, self.OnKey, self) self.SetFocus() self._bgBrush = wx.Brush('white') self._gridPen = wx.Pen('black') self._gridPen.SetWidth(0.3) self._gridPen.SetCap(wx.CAP_PROJECTING) self._wallPen1 = wx.Pen('black') self._wallPen1.SetWidth(6) self._wallPen1.SetCap(wx.CAP_PROJECTING) self._wallPen2 = wx.Pen('red') self._wallPen2.SetWidth(2) self._wallPen2.SetCap(wx.CAP_PROJECTING) self._stoneBrush1 = wx.Brush('cyan') self._stoneBrush2 = wx.Brush('white') self._textBrush = wx.Brush('black') self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) self._font.SetWeight(wx.BOLD) self._font.SetPointSize(18) self._robotBrush = wx.Brush('blue') self._robotPen = wx.Pen('blue') self._robotPen.SetWidth(4) self._robotPen.SetCap(wx.CAP_BUTT) self._trailPen = wx.Pen('grey') self._trailPen.SetWidth(1) def OnPaint(self, e): dc = wx.PaintDC(self) gc = wx.GraphicsContext.Create(dc) self.paint(gc, self._world._maze) def repaintSquares(self, *args, **kw): self.RefreshRect(self.paintBounds(*args, **kw)) def _stoneSetter(self, x, y, i): def f(e): self._world._maze.setStones(x, y, i) return f def OnClick(self, e): self.SetFocus() if not self._world.editable: return near = self.nearest(e.GetX(), e.GetY()) if len(near) == 3: self._world._maze.toggleWall(*near) else: x, y = near if (x >= 0 and x < self._world._maze.width and y >= 0 and y < self._world._maze.height): menu = wx.Menu() self.Bind(wx.EVT_MENU, self._stoneSetter(x, y, 0), menu.Append(wx.ID_ANY, "Empty")) for i in range(1, 10): self.Bind(wx.EVT_MENU, self._stoneSetter(x, y, i), menu.Append(wx.ID_ANY, str(i))) self.PopupMenu(menu, e.GetPosition()) def OnKey(self, e): if not self._world.editable: e.Skip() return code = e.GetKeyCode() if code == wx.WXK_UP: try: self._world._maze.defaultRobot.move() except rurple.world.WorldException, e: self._world.handleException(self, e) elif code == wx.WXK_LEFT: try: self._world._maze.defaultRobot.turn_left() except rurple.world.WorldException, e: self._world.handleException(self, e) def coordinates(self, x, y): m = self._world._maze return self._offset + self._spacing*2*x, self._offset + self._spacing*2*(m.height - y) def nearest(self, x, y): m = self._world._maze x -= self._offset y -= self._offset xx = (x % (self._spacing*2)) - self._spacing yy = -((y % (self._spacing*2)) - self._spacing) x = x // (self._spacing*2) y = m.height - (y // (self._spacing*2)) -1 if max(abs(xx), abs(yy)) < self._spacing * 0.58: return (x, y) elif xx >= yy and xx >= -yy: return (x +1, y, 'v') elif xx <= yy and xx <= -yy: return (x, y, 'v') elif yy >= 0: return (x, y +1, 'h') else: return (x, y, 'h') def paintRobot(self, gc, r): x, y = self.coordinates(r.x + .5, r.y + .5) gc.Translate(x, y) gc.Rotate(-math.pi * 0.5 * r.dir) gc.SetBrush(self._robotBrush) p = gc.CreatePath() p.AddCircle(0, 0, 7) gc.FillPath(p) gc.SetPen(self._robotPen) p = gc.CreatePath() p.MoveToPoint(10, -10) p.AddLineToPoint(0, -10) p.AddLineToPoint(0, 10) p.AddLineToPoint(10, 10) gc.StrokePath(p) def _trailPoint(self, p): x, y, d = p s = 0.1 if d == 0: x -= s; y -= s elif d == 1: x += s; y -= s elif d == 2: x += s; y += s else: x -= s; y += s return self.coordinates(x + 0.5, y + 0.5) def paintTrail(self, gc, r): gc.SetPen(self._trailPen) p = gc.CreatePath() t = r.trail p.MoveToPoint(*self._trailPoint(t[0])) for c in t[1:]: p.AddLineToPoint(*self._trailPoint(c)) gc.StrokePath(p) def paint(self, gc, maze): gc.SetBrush(self._bgBrush) x1, y1 = self.coordinates(0, 0) x2, y2 = self.coordinates(maze.width, maze.height) gc.DrawRectangle(x1, y2, x2 - x1, y1 - y2) gc.SetPen(self._gridPen) p = gc.CreatePath() for i in range(maze.height +1): p.MoveToPoint(*self.coordinates(0, i)) p.AddLineToPoint(*self.coordinates(maze.width, i)) for i in range(maze.width +1): p.MoveToPoint(*self.coordinates(i, 0)) p.AddLineToPoint(*self.coordinates(i, maze.height)) gc.StrokePath(p) p = gc.CreatePath() for x, y, d in maze.walls: p.MoveToPoint(*self.coordinates(x, y)) if d == 'v': p.AddLineToPoint(*self.coordinates(x, y+1)) else: p.AddLineToPoint(*self.coordinates(x+1, y)) gc.SetPen(self._wallPen1) gc.StrokePath(p) gc.SetPen(self._wallPen2) gc.StrokePath(p) for r in maze.robots.itervalues(): gc.PushState() self.paintTrail(gc, r) gc.PopState() gc.SetFont(self._font) for k, v in maze.stones.iteritems(): x, y = self.coordinates(k[0] + 0.5, k[1] + 0.5) gc.SetBrush(self._stoneBrush1) p = gc.CreatePath() p.AddCircle(x, y, 14) gc.FillPath(p) gc.SetBrush(self._stoneBrush2) p = gc.CreatePath() p.AddCircle(x, y, 11) gc.FillPath(p) gc.SetBrush(self._textBrush) t = str(v) exts = gc.GetFullTextExtent(t) # Correct for mis-centering by hand - bah! gc.DrawText(t, x - 0.5*exts[0] -1, y - 0.5*exts[1]) for r in maze.robots.itervalues(): gc.PushState() self.paintRobot(gc, r) gc.PopState() def paintBounds(self, x, y, w, h): xl, yl = self.coordinates(x, y + h) xh, yh = self.coordinates(x + w, y) return xl, yl, xh - xl, yh - yl def size(self): m = self._world._maze return (2*self._offset + 2*m.width*self._spacing, 2*self._offset + 2*m.height*self._spacing) def extractKey(d, k): res = d[k] del d[k] return res class ExceptionDialog(wx.Dialog): def __init__(self, *a, **kw): self._exception = extractKey(kw, 'exception') kw[str("title")] = self._exception.title wx.Dialog.__init__(self, *a, **kw) sizer = wx.BoxSizer(wx.VERTICAL) bitmap = wx.StaticBitmap(self, bitmap=rurple.share.toBitmap('ouch')) sizer.Add(bitmap) label = wx.StaticText(self, label=self._exception.text) sizer.Add(label, 1, wx.ALL|wx.ALIGN_CENTER, 5) sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) btnsizer = wx.StdDialogButtonSizer() btn = wx.Button(self, wx.ID_OK) btn.SetDefault() btnsizer.SetAffirmativeButton(btn) btnsizer.Realize() sizer.Add(btnsizer, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) class StoneDialog(wx.Dialog): def __init__(self, *a, **kw): self._maze = extractKey(kw, 'maze') wx.Dialog.__init__(self, *a, **kw) sizer = wx.BoxSizer(wx.VERTICAL) label = wx.StaticText(self, label="Stones for robot to start with") sizer.Add(label, 0, wx.ALL, 5) self._spin = wx.SpinCtrl(self, initial=self._maze.defaultRobot.stones) sizer.Add(self._spin, 0, wx.ALL, 5) sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) btnsizer = wx.StdDialogButtonSizer() btn = wx.Button(self, wx.ID_OK) btn.SetDefault() self.Bind(wx.EVT_BUTTON, self.OnOK, btn) btnsizer.AddButton(btn) btn = wx.Button(self, wx.ID_CANCEL) btnsizer.AddButton(btn) btnsizer.Realize() sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) def OnOK(self, e): self._maze.defaultRobot.stones = self._spin.Value e.Skip() class NewDialog(wx.Dialog): def __init__(self, *a, **kw): wx.Dialog.__init__(self, *a, **kw) sizer = wx.BoxSizer(wx.VERTICAL) label = wx.StaticText(self, label="Width and height of new maze") sizer.Add(label, 0, wx.ALL, 5) # Cheat - look into private members spinsizer = wx.BoxSizer(wx.HORIZONTAL) self._w = wx.SpinCtrl(self, min=1, initial=10) spinsizer.Add(self._w, 0, wx.ALL, 5) self._h = wx.SpinCtrl(self, min=1, initial=10) spinsizer.Add(self._h, 0, wx.ALL, 5) sizer.Add(spinsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) btnsizer = wx.StdDialogButtonSizer() btn = wx.Button(self, wx.ID_OK) btn.SetDefault() #self.Bind(wx.EVT_BUTTON, self.OnOK, btn) btnsizer.AddButton(btn) btn = wx.Button(self, wx.ID_CANCEL) btnsizer.AddButton(btn) btnsizer.Realize() sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) def makeWorld(self, ui): return World(ui, self._initstate(self._w.Value, self._h.Value)) def _initstate(self, w, h): return { "width": w, "walls": [], "stones": [], "robots": [ {"name": "robot", "y": 0, "x": 0, "dir": 0, "stones": 0} ], "height": h } class World(object): def __init__(self, ui, state): self._ui = ui self._window = None self._modified = False self._modifyHook = None self._maze = None self._maze = Maze(self, state) self._modified = False # cheat - use a var instead of a property self.editable = True @property def modified(self): return self._modified @modified.setter def modified(self, v): if self._modified != v: self._modified = v if self._modifyHook is not None: self._modifyHook.update() def modifyHook(self, m): self._modifyHook = m @property def staterep(self): return self._maze.staterep def makeWindow(self, parent): self._window = MazeWindow(parent, style=wx.WANTS_CHARS, world=self) self.statusChanged() return self._window def repaintSquares(self, x, y, w, h): if self._window is not None: self._window.repaintSquares(x, y, w, h) def statusChanged(self): if self._maze is not None: self._ui.setWorldStatus("Robot \"%s\" has %s stones" % (self._maze.defaultRobot.name, self._maze.defaultRobot.stones)) def menu(self, menu): return [ (self.OnStoneMenu, menu.Append(wx.ID_ANY, "Set stones...")), ] def runStart(self): self._maze.runStart() def OnStoneMenu(self, e): if not self.editable: wx.MessageDialog(self._ui, caption="Program running", message = "Cannot edit world while program is running", style=wx.ICON_ERROR | wx.OK).ShowModal() return d = StoneDialog(self._ui, maze=self._maze) d.ShowModal() def _print(self, *a, **kw): print(*a, file=self._ui.log, **kw) def _rollDice(self): return random.randint(1, 6) def _inputInt(self, text='Please enter an integer'): dlg = wx.TextEntryDialog(self._ui, message=text) try: if dlg.ShowModal() == wx.ID_OK: return int(dlg.GetValue()) else: return None finally: dlg.Destroy() def _inputString(self, text='Please enter some text'): dlg = wx.TextEntryDialog(self._ui, message=text) try: if dlg.ShowModal() == wx.ID_OK: return dlg.GetValue() else: return None finally: dlg.Destroy() def _proxyRobot(self, name): return eval( "lambda *a, **kw: maze.defaultRobot.%s(*a, **kw)" % name, {"maze": self._maze}) def getGlobals(self, t): res = {} res.update(dict([ (name, t.proxyFunction(self._proxyRobot(name))) for name in [ "move", "turn_left", "pick_stone", "put_stone", "on_stone", "got_stone", "front_is_clear", "left_is_clear", "right_is_clear", "facing_north", ]])) res.update({ "print": t.proxyFunction(self._print), "roll_dice": t.proxyFunction(self._rollDice), "input_int": t.proxyFunction(self._inputInt), "input_string": t.proxyFunction(self._inputString), }) return res def handleException(self, frame, e): d = ExceptionDialog(frame, exception=e) d.ShowModal() __all__ = [World, NewDialog] rurple-ng-0.5+16/setup.py000066400000000000000000000005401124731324500152260ustar00rootroot00000000000000import distutils.core import py2exe distutils.core.setup( options = { "py2exe": { "dll_excludes": ["MSVCP90.dll"], "dist_dir": "build/py2exe", } }, windows = [{ "script": "start_rurple.py", "dest_base": "rurple", }], packages = ["rurple", "rurple.worlds"] ) rurple-ng-0.5+16/share/000077500000000000000000000000001124731324500146175ustar00rootroot00000000000000rurple-ng-0.5+16/share/COPYING.txt000077500000000000000000000431101124731324500164720ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. rurple-ng-0.5+16/share/html/000077500000000000000000000000001124731324500155635ustar00rootroot00000000000000rurple-ng-0.5+16/share/html/index.html000066400000000000000000000001651124731324500175620ustar00rootroot00000000000000 Rurple NG

Rurple NG

Manual goes here :-)

rurple-ng-0.5+16/share/images/000077500000000000000000000000001124731324500160645ustar00rootroot00000000000000rurple-ng-0.5+16/share/images/ouch.png000077500000000000000000005706721124731324500175540ustar00rootroot00000000000000PNG  IHDRE pHYs  IDATxLWeYvޅi+]j3~F !EI$$Ho" E"A_HQf(h4U].gDq=xo흕Ձ79g}kSojmۚiH A(Z4 Ø*eZ'''nWeIhl5/. ox9&\X,+ www+Պ,YqH"j^\:Kݭ)[Ğ/hjlaU7NOI{yYV,&ӱ(J,(vv˲>HpQȊITL)f):aEV?D TRFY)LG[7ngH`rckwXLⓧqֽkkWGMV{A,y᫭򴌼̛YRڨBT11zbvzf֋b4+yfōզb&^Mբ\Lp]}~q/#]^ꮭ.߿^0L$pyӌ]7^,fѳn^^[Nz˭Jije`\6͖/_`- ?^9wn\[ !pB\̌(/.+2TSdU0(\۲0#u70:ie:(Ew:Z)JR6-( V_fhZ-|IӀ99B |ӃO^ E&MZ0<^xsi6[[[w'e |.L\ZY><:::>ۚ}pO?нHB}xx0Ng5}0 I``H"ឌa$1@N،HX^S7JD[PXb&)EW)E01V%Le!hu(~~ :|ڬIT啞 j O(O`rۊcLÄcZv;2S籄٫'y9hÌeBƋYeÓGN'q0(a.a7 0"pdǁdlpQJ=ğ6߇Ȕ;b0NY8,Q2m%ySIWTC-YQ+^DLjHn`9Z'&!6:VTFӭBT\O8Or$nAXe+,ۻ8b0KATm]AOsx`yR,r~4x!0  Hpw.#'Td+I6TLLlFaX%D,{I*5˜7?"U'2?"Z%IhfeY@.v&a'>`@ 6'3I$G()R`h<[ Y z -vAm-EY"W]CKǟ}7lt{Tj|!E^w:i#D+ap]k㯛%ջn̓zO(J8@q 8M.diBIr"Ā@~ũ;k[ j __635vm4uu;kR0iV-I*/1@K0Ȥa%'HM š#r,e $ӳqkm/GIw٬]xҭ v*{j&+)',%{.kpĉU[a Ӓڠ5 qRuDóRHY`,֘( "V2-jhVm"\NfnQ׬j@N{+Ks4G` L 2U( 5J.cC҃$D 0P3lc06GS{_qk>A.2b%2N t&"H!˂Db\νCLU(O&gn =G²֛r,{BњeU2Z̪JlgWv7&OZtۻ;`rO_tb6M`H:dp XsQu sZ|8pa\‡ It BsYErB)dvnj.`0^D(@wQV75@'O&GvjOZՆgIxzG7VlL`Aj&EY[M"[E@( /%HUs}wcq<tYQ_s/󅢑ّW)b#T ՄóŒ8?#YtsU@+ BPO|A3@"H4H}@d6FHhCX*Cà 傼4M\ 8պT͟[[9?ȿL nM$!y,x$ Qz `wsOEf8$F@Uh>ȗe?NC/+겐R` E-Xo8tsFyZ|s{_(ČRd2j,8}UV , JdP4jL! Z`H$y 85,JTf^t z!N N؏(fVBIJ\ \6@sOrttpmυiEt+"/ǂ 8G4C\ Fh[%!8J.FX!lZfB Fs]1!L7NWAV KSW/";ѾL¡9e`>$``&|E`}.Lq|R4!yI$' d8 IXV8Qp9HDCJ: G@b\ڝ'M]#G{n),ƃaAgxFIUEJ"U'cSҩ9t|dnOm,ώa`ȊGaVeM4RWoI ଄hQYg.fņ:{VezLFo̍X08pHN&UD*<_-[R8MhnK#Bq! ט `|)AR$&aյsY̔ŦQLsêWw\߻>mʥ.HեGOwk ``\2DDK|= Y‘ЄS@ 4":B_-"˩Ί[ynUwA\-;G""c9s/),Sr,rrQJIBD ϠAPl޻~/5'AFkjCBE*vssKFl`>$ޡpIHJOL9m=q -WXb(ip xYQgG-#b\~˗O<uBz+h$ycыlxC4&%Pgg }ggYu6r'=,ڌ(J$p "+>x( 5q!1/Al}Xp8b_|6L1>?;;;99gAw fD'jnz1<mˢ,!$Eu1z|@`l ؠc(iSK jhZϧ!Lpɜnаeq tŠU^F,.OA$Ҩ}@Y0Y:zM%666ݻT 'D]}ب|@c4يD3esVuzznXNcV b9FuRZZz4(ݯke lp;!X3Jod`%J{NB݁7TSLW_bfqUk\ӵ@d1{=} DD&Nm(ϛqd Y§,z=_ʐ拣%KH0HAsAi9 _ʋ/=|߸r畕e:+0O8UTiIC!d|%,aO2,ޝ;7vfYnf3O+<ÅeeƋwq`vMx8>?z_^^t;` Cƭp!~skj׃9v:l6a/ ڀ o0Vx/ 6[ n;p-2G1fmhd=[r_tT+}}lO' tKZ<0j珿ghj H( x,,U0{`IeX  #/b]Y$ }HX ==U xś/)sRlebk@ӡXUQ,5$V&qeeH8`O!*YڋLl%p4W\/b9&<(WժSnݼUBթyN׶]}O*0|S"B9V`lxp>$56j4dUnݺ&Ó"&VaTIy#rĒ5|p1?2_ػCVEVQ'h`0)?h?~ ^x:._+'S.{2AG̦3Z%2D|΀=%?ph05k-pp4N&n<_\^ʥmbf+JZUsȃ~j 'wpFv 0=>0HE;*EVu EG"뀘 !nE%sHS )T/)8O2`#&qU,5(30ZP@9N`f$2%);,J2"AMA|'M9CD`yobf'xeYUu Kqr/ě!C KZ_QMbe'|e 8/1^oWF,o:w^nGU7{&d^ZZ_ȃᘶ0e Kv7Zß(_#Z4F9rcU#j֬@xx+9RSB?z/BKth$wm0CϧuR1H7,4%i^DEy=IT.:dSA,FK8c(^YkiY@ 5UX'jݛ0iZ`z0$7}޿PKXr+&#%~(`Ưz#j$ |F#HT$1t88w&J8HaziVYu`5eaqCf,ju%{T(_/}f!raUk4uPza[֜FRAHok2 N[T%Ώ ^<xyyVVOUm0^|eѴ%Dhq&;lT1mEPy)s/Mdž J&Z^T1AtYP5= <0RTm;a2[PBFxOV$AH/@+@074@ïT!Krh@ie^ 1KF8v:eM'I>M77_l5''F,JH&/4[:(0W2.4oT&|KUidʯpKL?,@"-` NF J42^%D, )9͹+`jBEYپ.ӚJWe]SlK&t #H6& ^ k+]qZϢ&?DlU)u*'뢬IL)/Vqoz)-P|<"w,8! ,QL" /8^0žyh @0ήƧgg?G;C\N{+*?Ch<Ɣ2e0H-Z1Qy["Nj68:|yqju L0TĠkln( IB?*4l&RR@xp0Tt ~b_ J/_}xj'>ݪ74byQna_0"+ NZ0U4Zd(zNUbT,9&\셦dTNu/n_?9<|ٶT}4qLMpB @%#!zQ^Kh%Ea v%)/Ҙ8u^ƲqQ&Fw<?{q{>)yHs*xi0UBAZ"ǃ)4*l1kD(!"<8+ӵsc41a2U Bt#cNONmZE- ê7j͋cO az2 FÏ6WQ . !0ec1]b2h ȩ 皐qPZhjƲ|^ZػǏhW{gc1SVL-N"&& neޮ8W!6MPZBA2ǖP, V%"zP0I$,i?W+*ڝ6(`0fB[hOndfK_~#l>SC[#,_;T* 4!x}2Xk[U(`e dfIye9Br*3j{ʊj;ķnMW'kP+SN׍yU é5_:7..? K<݅kTPx[^^BF4E`4p$Q5qu7_%Ώ'9-VNHPM g6ۋ:L(!opi.i6>scY.N'?N$\ܐ&$ZfUhly<u2R E~4R^`ʆ4Xپ *H*Ah놈X*hD yh5bi՘XlGэ$/^]<'7T4ITT C+fMhpFa(G&Aͦ3<ʃA>R5Mn*MXB]' [JTw*y5,d8DΟNS ) !4+aZ` <99݄ E%K%Z#)$ "Hj|%$jjĴ 9I wͿ)&(DVLO廒!,#2X8"x֚-IKzQ(U*Ng eyǟ|s7K˚pg{RJ8ڛB2\lu*6 b_}1՚ W`jJ;:Tituy=o+U?`'ܿmocB?TN6T)TmQ)SJmʥ4\^3$xp&p7J:èjUY( ?( F芕Ѳv4vF%NRX>{Pr(ShwQ4YV{m$Z)5 >_޼~; ]WuAYF6uQg W7xث4Zp?Uu7W d~관G9<9)*dghQVlF ?8P ] ;1+5O@EiW*BJ޼w˗G4yQa+''ǠDnVƞȨf0mci}\\^^v͝677dy4:Bbz 擳 L Ӹy&4b&(A[EߌfT0{{ {1\ZZ $Au}ڗMٺAVЍn{ =76˧'pY4YI QR$)v M"!V2mX!B6>;QBr~yؙټ/~!#ՋgIL] ڭ7& zz>NjmYV6W0O^) PJHe:M͔[l} ʡ6gYYHPSYVG+2'MBZ_Ҭhw4 ;+h1-8-q%$,/% ExP|qR.(TnݺM;_l{2]VB|~yyaoo^xs;|nt o%.AP}0OI/>׳'vc%UEV.\r[^7v[0h{(ݹy |*Rd9NnhECЩBV2"C7_,x3ůiJ|<ǟ]כ`hˣ*TW}cb:]Gn:}$|1xg9@Ŋ UzU? >$xau;3B9LJF (&]^\`F~I|#qW߿X, !Ui !i۰TkCX9s:ܻwW,`0a`ht8|* QeoumV[J2qj/aA[[[++Wxsׯ_ '`8@dnfQ%a^vN|"%]ɜd)Օٌ) l+dNhUE+5%"f\l'[c.C FN,98Tծ=ͥJ e{g s-܊CgZI.>ưtN 8][x0]&Yz TBl޼~*k<X_~UZh+x4͒V<~&޸ ؀=<QOK1$ ?gҳ3D.V-)F j:8 a?s]_[#Wʬʊ6/ iZ{w~_Zڋ/\u젛h*ߌX@*P2 dɦ)?˯1ooNNN1݀,j2@u0Gヒ?{l`?cX`@QTTcZg Ch-t#E G4"L(2%;rK>ϩ|%m$`f8_!P3XX54D? ^nnn˪v9ݸu; ˳S݁ؔ]GO1:Cv`"HR)uj6pt"T{II : 8?ÿsA]Uv$H Sᛇ߄~@_|`4k{FӖ5%/06@2~)&@"зl<-|*LW^ݽ{t*Q'''f?Fc0~{ޝw=|dZiFS~;;H4ԭe }W{mA]V4{|fVw>8Ĝ%]k:@d<\H7EZHJ*:b<Qbn+H(3? tJlmfeI~uq6M?޼yfǰz4!o0ƨ8W" ^|rJVOe MClmmí1O*OS@?:,YmzmoY䋯V:) !X_Xk+ ew# 60 `e{k3\ sOcٜ+} '|hwWQJ, <[_^ '`Uw,ݝO-]L,s9&]qtQ},I{{43ȓ[n!dy ̢iN!U ;wxy04tUVRƧ0%8*lydZZ))UksInIBhonmy UEh>X׻gijQR _m" !cJ/;67$BwLk<*JNIUOE[iSX ܧNvIqԫݥX1tAU*_6+U '1> _JuŴt)&aEK?.~fў/4pIWzvw?{l@DWM E ܙ^R  4JJ37RщkU+A [[n^NuD̢׬c^7n:8|j@FƙNRIx_W~eC'"KŲD!޸q~n'H4Dg/VMG찇[AT>>q 4lX^ڿC=Jj4bK^`BXm4&[fP Of бy6x{7&C:r*3M!.Aކ0 f),H" ~WR33ox 66eӱD -:EZ gFS$Tv&HswE8ȥD?U5gQYե]l7UVaͧsM2R.#0d_\wk~g?(wEjJ.UjZT.u*buR1(m˘*. pRy}ᇘc~vk{{omUXTIޠ@edi]UZqg: vϪlFgKT^[/5K!̣Z2y⟊H%o'ZS2l|2޳ )M2⭫7oO+ev ȞP\rJ:T'FvPIYA0xqQ$:Z)˙L[wpq{6"BA[Ʒ5dDNFVǮ{sCs9KWc+{woxr EU9tp:)}k!Z쀔7TjIeAΩNS2 vK++V0M1;N;.uLCk㊢^u%,6/S>$Ro-PRÙ v e9: QANirbjd 2vv2O޹q*718G+m.c)Dy*EmԔؖq{fbG8zm"i3 ±*~C[=ΈI8\YNf.lc̗mnōl>g PI4 y;R#`?H@ 漆b 3TMreY^/"ݶbU UKŋ* ; yEF 8zia5ڥ0B4 <  8:?\_[ßdz|:(+=ˌ6ӆUim:Wupl19€JE C&z(a@b I]Ra:67p8ve5R*A1It'%f-ϋ8hRݑ:]`g *I-?H:\#ߏb?vVnܺ^tض"Q#g T&S"kT&HĔ}iCջ);֩sJFZ^Mhkj'$WLĖ,@KQ 8u'"?&P_*bt@#Cϧ|B 7WɔCVHh7S FIpbGۻdd*M#$dYdivv%ƾGUYkpfgICMmB", `زA  , ۲ 2GCM]ݵWfUľ}9U2#o/||ӟE\䠊p<9$uZќaA^!tA@IZt41ٽnKos[b^tfº˻bV+H/'ڬ4!~r9E͗+Exrvq×/gqU )2 &j jj"f8>)1Nt qĽ~2i.T @k eOY6C&v[;["5 4IFq/M- cFw$B=u VO6a9'JOfFq|Awp`TyxJTbOFl) 4 /# \54k>pV GPpπ ^вua.F*YL(Ât+iQ #ēfx|fFxfidMFQ-Y<{f ƈ·|)*r/.F!X9E3pӬd3A|*KH=#qD5=IOv6c (RN%Ʀ/ozes !S8DJ& KRE!;c>zRn뒦9>6N+ay^.ǽfdӬW+Js˽tqK;Mb|< xDRt#u,#;b:A>v{cO>֕`K*"/i5eOaP7,βEt&E9[ Xw~v/ ȒEѬDҵgWw| ˞i&T*, UBa֜HDUD8PY'_>wn\[B?1²4Y]7 mӢg <):1O~ħ8p` 9s{ˇ3J = PEϪ'x,Qg)R4Moz.Ĺ롐D kǽ[ܴW.55 RL4".<6X%?Lߛ"kZHR17ǓϓJ?ϪZՂ̧ :ua">ɖokj#NY!a#O0Tenku0% *VOQAtČICqE?xXx;Ϯ]c/Xh݈.>`:x)Fn^(`6D$R-ҖԮ$IƜ.!4ɄԗER<+[zw/)z.7e1;a6QS4X.+lA0P6ӱ\ey^op<[*p.BA9RKW' *b*[>X0 j/^<9;83˛[UGdzfs*/hAnf M&sbdkiT M q'%+5"R: 92~a ^䳽ӽ_ƛr5xYBRbV:eKT&3DŇ  [Uh YI2 9AV yi]p#cĖ,'hPQ/T(ŵ~9،\{Z>uoDF<0`*!VbF3B)~]*c $W?qZX[r%siDd_2jPO(8Getġa*ȶ/>?t57Oe.iJDVMg I $TmF F,=6ă SX ,ˋggjl\h؃RHV˔')jWt=g…ռ("Z|0F$ 2\yшZt}Ɵ_`B^ Ta AI6`Y<%u6&.U 9L8pr]Ԫ~,;~b~\' |ΠB"Dž0xVmw6ֲpY+j[X+WFZ@\s|0UIܺu,P}@a l }1cX; ,&^3X0qǏpFcb醴 p2+,,c+\@5D&MpYp00xB*K)`MhATiEIDx3LчMh3QXiJ. `i~~<%V/2|y˨ l4S!  {w$CxŴv !zu\L0ĥMQ%U3Vjy^a+W.R%,ˆz0mSR_BM*ȿɿ!OwpTIh9,KLm9t4 KCAl>veS7N6\#QC?iA.ȡ,Ċ(ġFAHk}dh^|NK՚:1/S~.E>mѩ9{<6sxؓtuuX(¸' A4Sb3eaه?'N౔\3 Kj%l^{F^UH_2}}KKf`?;ջ`5*+8Rt>}4D6ˉpԵ*2 QF!6[@DP@v"#_Xl[y~"A9 ǖ"D ө>Op_ɱ5yFQFω4TD;r7V;V )JRT&pU65Jm ~27>WbR94:`ZeG^zj Lggg ^d2\2eȴ"kƟ ǒ ~mˬaذm3$f0#B:tKLEJLT.$ah^\B%YiJM," 25Ox{yCQ>`jI SNR$ID(a)9.|uHk G,F㉪5Z%̒ThhRH}yC#qiO("gV۷nŗL.&fũǖ\@Vd1 2DĚϓ(Y?a%O F(Oܼӣïo_.D)@9%J V .`X?Ef`O+(b)F)YQa<0{;OS9~z՚0ꊘOb.L!FK^Xѹ9%8ir&{KeSL9̺T)|CY  p }pK$Nd(qJI6/~dn7eMެ K.G9=1'(V19 K3Zpzhc`.^g͒rSi [Xn/'Qői]@o@2, 1d&c2=)0|eNF}CS<*,Jч,E;Hd0W7.K/!">6j1Vß$0OmlwIّT߇ C]ΦFHAGa6=b5gzF tKWpS*7JL-ddDׇ! 0Jg8*U+|*Ĭ,{Vp r"c; 4\dBv6"x+pja%ʒM)#ab8\cXϞ;'a ޼wU)bn1#4!]Q2OD`L*21V'_=*yNp|p7׬G'BFedcFPm !V @(t3R|wR%'%\VTB^:6^")ғ"X&"xe ˃W_m֧H^!&ioniy}rY7;I-,])F3)3?9Z=eaEep-G+I f1<@Mݰ QTjd<ˇ!YK~=tתt! M7hv7X #wA,RXp`4h(j̝iESgߗ7o)oZ;umiA/6y$Fxha %#"@9OgGP8>ɢQwnTp~-A$ض얕:m+P4{/"5cg/_޸r՞vD;ܲ֔PT|2/,s3TT*-miA\JyT[++62555,rZP%Ud`bXS/eƎ]^x  nLKODU1fş)) {DamyHU (4J;DL< 2/#NETvx 9.bh9)L$ -A0˧rIYW1 HZ VPyynnasYPPHm6}($=[7XTۅΓύJc}|G B WW;Jr|16&UXzYX3QUKU[{ttrl`O@V-RIx&,bZKѥiRvjemlk5[6Jh$2}Gv dT6FHha^fEj$ %QyFÃBcA1E:CΥ@)1RF[u5|㣭 mbQDXh2nڧ }^fAbL<]}f>mR1]$'a*|yī]I>L0 /El;QLi\1<=Z.E7O_͜\ +t(aA0KKӑ!#fF56RTx:3b^ fw|zE!V؞,Z,4*dc95f*jBYN"+)|rq{;oFW FyXhVPE@u S?ȇG㏇C'p$B88w{S,|Tr"?.u;,d~&.ڑ ZUY:^6x:`'ty;r>ޝ=,#ed '6F4Lby:m9xFdn9lnʕԲL9-i6 h9K! m/h0g8A*R%>A_7Bѱ+~S bTb!|s$] 2)P^yM\)']0g}y6t/wzQ-<IZ.eᒬ#KJ138ʱY=/OTjO<2PKP.y͋T̎x0>["0|0=\J@B ?<8f6\upX$uB$^qUL-{v,"NByb)~(sgӼfAE!KRu,$ v-"{3ʀm>) {G0~xzj~=k[cSu6Yk-# zTCKP(wܶ)Iʹ<;5ڥK7xp||>\ zx0!]plzT^LAErrVkuZ?˗/\XQ)2:rdjq|zvK%@|69W*U]$VJt0&Q* Gaj][\SCJJ>_^c[t&+0ۇ[X4lSM'CH{$b$ BSu%;9C5a@ C7(!@byYI=p:ªVkT<_a"tvzA;D0<%neCNʤ$yfr68Kb/iڭ_~]\v)B;ˡ C|2Rd `yldAYrd2V@xպr2|/yU"/Λn\po*k EKocR){9'p3GCgyMȀW^)k[z?`xf|kE}ZvsԔ4Q5?]1,HǤyS5zۧˁ,yPkL4F:!R=zDTuh;Qp'݊cm·2Y/ʖm#$ Z!| ~h5)$>9뷚mY̹kJNlĴ-Eq$ZIN #ױF s8tvZY[ܽ}ˇlKVJ26`.g9ñLL!-.$a n>u6 'ٙbJAO:r "&yEUP0`2L H;b{3F8 R8t٨zFNE X]J|<.KE+z-Q}g_OE/WW߸%>;M]MU=Q瑁EV5 vO: }v摉AKȬ  hys8^J66„7nݼ)KGj˛jqPY'rc.Vb=k\ 1=̣֒;y~(ĨE_>xm4al%,FRĠL(RpgU͊h`5`F%JZ <%X(Q:} seboSp;5F^ƣJ.CWZShv161i `CX70^f>NǵjL 7oJ7uى#98FY:zj9Yb fHȽˆ+}2j f% o}ݳaY"g`Y L *G>Db~9.8\i& )S)Ӵu:xV}to<bo#xP@T E7<p4NuE7 0Xi%N g<ʤ\ο+p8h<3&(bE~굝]k C0Ry6j) VX'w6 zxoۦ5yrv>L/ُL"f " ྊV*!&u?p߾[')$d?ŸK^R[\`a矣/pF>޻x<OO%$vZ7bGk*0İM@/X_ e9s(YS9xXo4nݼcZ3Ϙf\'cECUMl嫹RwރꫯN`<{~I.m_t`Qp:aF  X/ۭ.fjG7oKz;> ^ xj+8K4V1P(]U6O< 7hևXjiї9,trBw2˃<,0Yz c;A>8iؑ=j.l"2#oأ:6 6VFضpIDȧ̽QWF!a }Q^ϜIZs!*, BPld>%u- F2:/D5>0 v BgBciȹyy7۝htҥF\>V^Zt;a}GrOӊ!Y0hք %MG`;ۗq h(>ُz\ EKe%Uk< C]ɈYZ0ɗ (Vwf!zzE |=gs?a'klZ3uk;/^1][+$7Ms bq}*$1#mYŭ x.' ܟQ !AXKݻ`ׯ_TKTݻwaяކ1<={Z^!~ч5X1V(V=0 FaxϧO}kxܜnq|q8)++O>m6a޹5/poFSUuĜQ*M^7Oiѝof><98^qǶaOg"76 [omn] /a767Aimn_y"3%Uiț[`9]S+p+w̒:hgjG8j0'kGaŋ%־-HG#_bߢ3]?M|YT4I?|FϞ=*2F>#<]umgjfH[;/'kkd ֆw˽ܪ[nKNgy[qfgV*ޭ>I^|?GbZ< h4giOiwNJ틟}^M˶[4a×J/r2\ZkX''ӱYypo<k50ǀ|UFny[Tjͭ41?߿s.C&L-ăԈe9PS-U Qd@XXw m؟'^1E/'UK׮s>~<5ͭKK岗{{|=[o G}#oHa```@_|25W`;App{nuWV}Ž>yX.ŤJ__ 1LC4(++S@la*y|w.@b? OiET<%j8=ڗ"^79+U7MR>9RP'O #C8)ӱrVp" K28Fނ{),jJPWjtj,pbIH|ᄉ]y2^1OQrpeXg?ߨ(f3`g;zMxãܸq{Ԯk`1s o䊝;oRKx]vvvz² *KJc skɹG EJUb0كgw pۮ7ۜ*3OJ4|’BIO>m;kjwnvcRe91ɫ o3>,IVxI7j.7L8%?(:VEN?XR)2ϛ:-y2Rb3n j^6Ly)5wMLb (T$ 2nQ)Fa0<9xT:aϹHRp3Hk4&P-rJk_/ya5 @x'< x`Vd:TxhJ%@.S2!(H]RNӸlhZ"S00V0^T M\&A\pWd^"ER`M4Q#iAl8SJ3#:fbzQO`S2|v,djgCD'zRY9\GHh#|6PCR%22` "* ^34 HX(eHH`_/qwGPk^3=mɀOxߋlȖ)0rsub9Rkp"`[&f1Ī+7oހI48&áAhkFi "ȣBڕ]5exgm཮*PͧvRo49@?WqYh&j/tz 7VŔn7)!>~|s`uw:!Xx1lYUyѶZw蘽FM^zRny  ]mta\Ymdx$03{ @i|umuwwq` Q$_ɬ* ^GuJ0RU秒*>ģqGwnwVG&A$ž'D>Yלs%Z Q4 [_] d4BsFIf]Et&P@HxbDQEgax38 ԝ!xY%2u28 K].0('JMC,ew\[a+S?DM[SF}(SOxjy.# CB)&5|S bPQ0b8SdD2r +KUrAOy1_Q ubZ@,5x]Y*Q٫lٔ%Y@H֫sfG_XDvceDtH giiJ|-d-36c'tsr^ʢ߾&B.R~IZ,? -Z<~ $~giHA'Kŋb`}Z c>8wĴXn{J^R)ÌӉtBZaRsg֦93!"1`+" KEbjjNGj ;7mˊcKGl&[y*Þ]CVNh0NRYm6$, DɧEs|Pu-\z9Y;(؍F AH+29 :DZxFkv \ e8e $BS4EJs>%֯,9!BI:98 7S;ͮ=zm6ۀH`UDm4Hig9Mylgt6%AuR3ԘD8,ZQQ{⃇/JNjx $03;0<#e25s]3#C0$iy>r@'--`Xna ęN^8?Y$rP`sS؂:s۬5A56ocN'SUS9%L=eA;$9N(U$SmtTUM31ۍ׎Zo/!HR 3AY6V)J`o>e?0{ǣ;(yvb[FVRk-xOu;w`8nT F2z{| ,HXG.CI4F2 DaQrwzN5hkݣ}1XrZty?&,MbV@Չ*i, +kFy<]}>`#ʇ/Fߛ֚ͺȯNN)sR ZdLg*H CS%)/LƨH4HFYI(.F,؝aB<'@@ Hq]b +lZ^n5*0:] x*`#؋ym 04NO$tRhZ/^ʭ%&(Up"ʋ""HEQ1u`9"/+qdf ) CHFG(#rB yB` ƥ#DgbB86LA`J[[/n>9ߺV !6>a)JL1Pg)fpc1ud%4BO5gb:D֎3Ecs^RE8>/jD> ^?"u!g3et;9>z0gRvl>dGūFᕍ[7d!;$ Y^,aA–Nge4-Nփ'9@J]7@z|njX+>m]%_ ;wr2 v ҿʲ iIbӂ$R^fB[xԍ) BgM|LvXOpſNbI\M^g萎NE__]k"TiZ'{[[ׯ\<{XBߞd.,3jy߶?>:ʓtYKA]pNk4}x"-E|($|.8jElt'n$QCvAHkX?&j1~N3uЦ#.,`s8.>7fDSJdpD9&j|㲊v . ә!>#vNSPy~T[`6"?ۨV[YDkqs0FR O"EZy@1X"TGIV~)O6c*"8@hJ4$B ܒ Dc V zB?ZZOjO5ܬCb.- ol;Q"Lj>@\wtѼF^#`et .[dxw t] h>lsb:G`ql&fOmbVr}fWgi$ai 3A< X+De^^Ģ;^_T,ӂE Q%IK*ÔzfL!(+Ovsw?1 LJ7X 0F*'JBl۱efH ֌6 " $[" l+XONKz¡2HtM7 7S~6NC,TY]2 +RDA^>QG1ז7&16/LW1INGa_ =cX*P5FIo~te.q5z~z0esx'z;2%+,<D+1Bi"WE~W*:TlHJAQy3$@GHNs5]Q(sat+Dэ0 PbNe5Wl`E"6K2֋ G6c&E*M%Z۱ Ƥ}\M'd\J8A[}?INR9a3.ˠ9Ê0 Ʋ;l0!o2(%,FCa{ĘXm Ŷ5'^ 29Z A(*hI㫿.罾kZ_kՍJ$ ]X66+Z]u92$X]y#/h15,zH'QXQ!S+FKKև1<`dxb {23X/}' d#iQx$.s?᳷D^Xv1¬ fgG? :F#")DgQR()Ã1`#BE yQqƩ\(g:Ӛav; 3=bV47bV/# OM0t*2z004RiMql4y>:bvIsBZ||Fi8EKy]xA?Z(! LE]ozrʅoPG\"\E~bXFq*S0 y!O`4km. yՙ/^lbS֢001Ees2H@8Q9!#9gⰯ\%Gy|~~ e51 +yJ"C38+>Zi+ּXST,@yTF-=;*9nvȒ:"L txW.?)Y(!XH+:lD"hB_0ѷ.NS+ Ɨ_|;7o^JT tw8"c'_,0xVDŽA<󩐱,N&zH/4P7y ?Rl :*EfWAe&e-3YRBlı4ټT. ?ׁ_󋨎l,bfaQlv~?KiL(G7<l915J"@>Bz??ӤS5ŕV"3},60gXHj|ӁՌ)klb̋!\gϢ~=Pg0+=cDisA`!9gJݣ= \/zVϪp! kYRv%XIZQ(yD2f'dq`VxIUַa'Bf:e_'3gSTT(B: zt`Q ܭ7# 1M[JRNFB e햀x9\=x`uuYИbY6-k~xG4t%&0ѻ9+3 +]%;F`TAZY ^Z/yWi1|>g>0+J{K~rrLUX;36R {1tr/>a>~1=;:"-ө13V̂dXm}N\ͽ8 42\&Lx J>o޼޻X_/=|bnqg{r';M\K/kt/0;;;wAȔL)IdL 6fR(C( a,bT4X-Y>HP#:2O.jR"y!)B.oS,!Vy+s.kjZT`>`1xZGߓu:']2sdD+jR ŭ߬_s'!!D 0ɲ(gױYlTV7SVP #h]a)aں}ݘs+"@L( 9墀h-M+zp:} hg+!2O9wR᷿w~|Ǐ&/d3dDiY5&@d r:hK)!c\X(-eq]Ԓ(/XAĒ:i1 I!+`RY676Ja ,dQ/Tf7 c*1rh0)’9*W^%&ͣ'Ub($.vdZF*qzæ)kaEƇg-a0BFu.W]w~pȘUxaa~2 G¼e؇ASR'c]vogzgHi5Ƽ)BlC"O iHEZ m4@ \ d yӥ%Z2|XmV 㜬Xyh~53>61S K9lS4$mo7rʦlq n=_ErRV 8ZVbya l +"LYt-*J>䧟~*HEQ,A sFFdס`].D 4%b15WuCļ)U$> Q:EaAvR%#!N /}PrcġT"'?ӏ?γO?nrx(y** |:JiY %*M "$x-M*LdՌxGRX_,dyTY`>3ƶ1jQo:y% 2x'D,Ɇ kHSEA'wXϣX3=/vl˜.Wsre0A ",M>뮯I\A Fs$mH" Pv e!A,C:5X6|VP;&=T1M=Ll!J/H*0 h2>ZE ,c(/ļtg>_ [ocVnD2ü#h0I2SLQ҂g(B8CrAjibnIBY* d7MFfȀ"K,JFFJ $-vحd(5EQ"J^*UȵܻER,)QP W"$ P3&E`1B$GIHKГ"sL7X (=ϚO繕d=Fwqyw&ο+3\b_oyMY'3<.~w/_샏VW*eO^ٿ4Z wRQ5T+b"w6rS_hpª+BVLU: 00Ka~H 77-xbNJ…Bb>J|ƨʎ{sLKw`=?6R&BqHڜ0nde Pp)qz I>Ͼ瞝8  !IEG)IN9ŲTRIYQ\J/[qlYD+RTTSX`ݙ=J5M#e2Y&Fg8mASO.6]ggn\`\TUKXG ?Ri2H`~B܄MàZGPT&x{PW3,b!ps7O7GG%(BӨmgk*Kajr8XDeԖP]hׁe?}|3z`cֳP(ax Ȍ?Ƥ!,r xAq[A ,t" =Ҳe$^ڹ`j;{ :3Gv"ŀ!3 8EǂK2SΑ&I$q`;0eKR4ͺ\ {&l)܆umOa"F=?4syARa)59~P=-.L Oy!KO]}qTL߲f wKP%yDbE~qkikO>>_hJty2q( K}M/ƙ:/q> o^RqU*.Dq#|՜a3Y+rYz%:#t4te7 y91xO`gݸze=\^T~sQ `VkśFgh;D(!Oi@׽B@9@MlӠ(1.2pliix&1̺.=0ݽ]]Nd NӔ(H`8L%aU!RZJNw{fb,\TåF{A4"H(a3 4 @vF(T  ?zrfYEM?hg*gU+0^3n9y[~xXcM;1Cv/XX"%N9n̂*jV\o5#+.-翸\3@fwI(; Su[ɨG>zkQC %=xNJejj0t{0u NoE5LfuO]#ra U!11|y$ҎDWi?tG3Su3WVȃ k/*.{coZ:!Gc*~( Rȃxqh؋$ICi<0p0 Kg8z0?z\TswuKq<{ux~ DvnB"I"ޡDtǪH:3Ylnz3p'(%>}D 'o[pai ,2Xe̬Wg>6D/L&vp4N,b4)($wY*DMg!IcgAg`T +TtDe޻])~&4Vl5)LO @`4Zx'B11BpiX=>fx@!i0TSFm 920 ^es7_= $9R(Ai&` y6 Egim&@N  9- rD`iX6{}/4+d1΂ab$PXvӊK_N&8PȻ5NlTQ2L"zJ81y&+N ҍjd {[3s{gC)QAfb$C䝃}CK]>8fz!@ /TEU1 ;n4;?wguӼpJuDǴrxJu$J%5VǢ %{mNqo=pazlfG$#.JCGh=;]"9&llnڎem7.8>W4 {J؏8CTgsxCtN*-hp≪@ 6 ^|₥b> ɻ{%m>w~M=zL5swIsSp.]}VW=EM)+Z(d( VoU)|%Cpgsy rkL2#e\$s~ Y$W0 W_=VFIVp02 Yݒ!.!i;ġgfs;DVxV/z a%g3YOuq\%Z q. #U `b"LJ)Z;q(> R,D9l6(N#Q{Ipǰ$j&B$Le&ܭ[s@ #/9IVdFqa^ȓ.צXC<\/d> nnmO?h6ڝ^XŌe%Eue@gΜ rDBA9L!B@xyD3dMl̖1&F= !(X4BYq4 쩹'.x}k< V\_}3O=iwnᩕ#;\T J#)yl];qro;3V{{ak,?^'~pd7h&51`Q8dN`~SN[?p0u{gΞ{ױ)"M/onm zDO--a LUJg_8y {\e b`p:C=fgq<r-'[<(˜T"/~sGh2sղX,fҊM \Gq $Oݸ,Kma4bQXL>t{ljO\k(G|$+"L@<pa~ (>֝;whu .eX+F8QsZzl:=Bc7o٥'vY]>tmm "oπAyTY(=(\y0m+++"Ű`Lh8* )|877)TCoZIݛ]X:-15\KKKJe BvUh1*b?H0?Ȥ7Nɀ'>OBrIC*i<6>?| LϓA> [ ڗLO2N%wp-ZꂍwpHɂ އick5P3S{?k?{$NPWN;{E@a 麘! X1 8:>]]6U|k@sR^TY>ID)@/+ (>ƈ,Lc:b\p{H{_ӑYhrw,h[`2f*`  Ke[<)Xbw_WaBuD8UQa,/o gsssuuն.ΆxR;Yaלc;mOhwg^K"cP`B$qz">u˗.}ڽ^KІ l hF^vB){?xhHnj @)L{YJnlN nO+d/\pM$/O`7c`oK2@rDVH#_V8q܁ 8\DceXUbཛVNXap|o"@kaRT=o}'ql._RHa1NbeAx|ԷeBtuK\{'Jszy~IQm`}u1$3DNgIӼq14)#DLǔalj 'ů(}X,*/J`D`.,,=x1OhXTP(eFCA+H& "X˗P.f8D*rx7m&qKBJ+8޶\.͍u w~d0SЌyuUPԻ=Z#C}iR, '(*kCR)L]dTrp4,+XէۙȤD`$6LEvL-<,#b֒`f g<b\E1}vw4{#z+%Qњ>ՓlViof7 iC|{ ȶDUAbI=AwמMOlo}cs!E84f-Bm:l |!;'e3S>IO MP3$#%MC҉^w]p0A'3xاh: :h.&Ѧ19f% 86,͵xgVV ${m‹}:ܻw35#kk{Pds$ Ghsw>/lBkPmE:5];h5j>\*-Q~,g؁8>OijBvRٙA:Nyl2%Q M^cTYa9pp)ϝ;[4K&z AoI-cʲk =_8E:{ÇNLcZ !\_뇍V:A]J8d@q ؄a/'b3y7[]ֽu8t Rr"R"[ X<A d;> z~DE:M&`qލ)0g1m3}s`P6e-q5G dbps+QxyULU4Y~ # GW;'$Njډ`Kb=o\| ~h0*v/ES( ά1\kj"։ 8F S"(Z$jMͩ-KF7Pkgu]UvV0'4u{Z#YH$<͑nÞS 3"Ҍ,8=l)*ĺC@Yܼ@R48"(7+RuQ$Zլ 촻fqaBrB{oBj*Uoٓ8@D"p`?s_ƄbJagsDC_7|C6JpMN0xM1LJ[Ixo磴K=8(aHV2B)Is@ PMiX.(M3y뽛e3򗟘ǣQ*CfXjƕ86̍(!W-׷ּ\M %+ya+H+H؃(vEaȫтBhVF X5C7QX*UdlBÑU.anaLoPwKHn?~xvv#_H+MX#7@lDkTQ3bP} t>wg=0M*PIRc^dᨕtb6h6[+67B?Η盹B7+0Gt^sR(9&A&jn_[tYV$@=(tkTPw8ncS( qd`4T FLe^{-~b9!M-qxJCFx mFS 8=pf^s|vox6[ƗX.$Qӥ>6ɆV%XQddhS|OP4pf. ? % VK{eq16pBxpjF efƎqY/0)CPʊ?䄰I rp-}?f|N{+ό0#< „y<,&kۉ$&C8(NX`bqbd>GMv+:5C]-ݎ.=>BB8'9JLhM`aZx,PݺGs:yvc%as<21 # H~ᴐ)FDS;akA[##`TMI@1ǥáC0`y1r臱͏z4 \2i8+صԟlB!^aUf(tIK0I81ƈ0Sm_lb{s4Ӟ#lw-|c:k$!8AF&RH1t;֠cqaAQ䩩*|p!cMOE <&6VSȪ$ 1v &IDP9& jcgcumLUĢMPs=XIΠ-eUPd! ` l1Mao*ízqif/TjE&$TrK~k7T\6tg{{3_݂a9X6ΉjS!T!a`jY>1BfpE@4jO[Yif@e]AvX0!g%,r*Lcָ~%J^`'9dCD^ f\Pxmon\ڕ'n2+,Y`Ev̓ „ 1 SfmD$dL`ޭʽמ,{ŁkA!1RZ 8 X"e%4~E[DYK,N*s}}Ns3SMit$֠&ģ,u< )&@QJ;NU)[}a3lvG8+< žH`%ĕV;(2`2b^WA])0z_IB}td]J2DiS 8 fy.TNSGZ,+s饙b=Xz:֕/I ZJxJ~.[C.JBW(-LʟnP_B[{v {9]?95/6݌+t@z%W C@ojFGGN>$l !ďˁy̬ K kTDSBؖAs\9EtLKH!2"O0 ORF^t%>HETBV7663Zo {8&S)Fo%2$DfqcJݮ!pXjқo9,D0;3ӵ}Y>%@5!5eH[,|㾃a %Q,%0i?ÿy3?OrwZ! W[r˅\ +Re0XѕMp[񌫈s6Yp. }P^wc+^=_4>X*_dNg\A|jܰW/v؋B[Z"C5E%8Wk>)}< :7_G]?is2<`}_c'A@b&~Xq5GGiP6җ#\zw>$rIask}XPi M8mS3A}Z>JtMю28KxVefnHTope)By dGK̢8XfՖ2M~{̎AbyCuN')/`z:<XY~`5er?_EŬ$62n%j֬VL0g݅J#ţVof莲<ք`5 1 e#?=MeǏ׉oy 1@b麊ԈáTEyБ%72c2 iJ6"t"fT/T%T'n 8 ^ -.Ɔn@j.xEc<ݵ3:ջsy ~u҇3e T|5 'EYX/8 f?xp#Տ\qqnnfH!ǽMd8nztH4Q/3 rwakVw]aL*LVDȀ_e!qAi±AK1dž0Ĕ&k q2[d>[? ̙q}\i<΍#36A(C/5Eb'#>q0RH2NTu"V8Ơ(317Be8޺Q脖|,PzDSlPv8 Yw(3 !=p$oHyg9Md*|(IO:,NsiE#qz^hZ0/Ȳ@R7e@ܤ`n™SL>2aJԗ!L'7N` #*ÎPĔ17ȧlFZQݿzޭWjDeMR~泜*#otQw2ܛ6=Nyck قhj%ETH^'AHH'kS1w#4e932uxѦpJbs(E ;Hìe^ 7 S4®y$~&;F^;w悡J#+rc|ټzewiϔh7bc3{wnLNhs cՄ HLJۡn{;Lnivfss,fP8k's G. mOTYL3"F}5ڏ[mS?b828K#jY沗z/՗_~x nq񋉞8-'m)zDEX)Z*`Ir 5NґIMpOP}($J@;!1C1h*d_$}SXg?dYd5}͠׽ ztR PL`i(`()H_Y;A45ɘ*@s\gل.-~X񁥙&kPj茏gaRX5G[{4 ѥ K3%bG$P_v(8ْc t&bJ,ǭiRrɊ" Z> [(K[J ϓ(cZf K2J )3'N`2[8<˜5]?u>8TeQ2\@04+L+B(aJ&c\p|5KqW,Rc9}T\(-8 i e0=hP3UԪ\.ǸuW=JbBL1+)jSfnԉC:X&QU IaY>qfzzOuLH,w'hlC2֌egV˰ o?Xf`u .?p#`?8s)*M'cwXʇ1iUB*'h3r' {H*ĸ 5m8T+Pr<,hO7> 4n X/<AUyWGc)`dޡIQr,D VaLItBfFY 0G[Aggg1dd̈2mN ؘAiԱt#f\t<:v;\n:)jUpMǕQ!{S_G5|"7``k-K:,ʩTTIKG" z]8RѳG.B;!],{}cg h4lFsdMui4Xӧgff|9NPGdZC϶BIcioٖFlӔG?]kZپy&#F`Za1Nl4r6(1S/PRK8\;_֪9:Hg H9C[珌HUBLYq& @3c""g6\=8o|޿ȨñVd)s EYh|=-3 0I 1h%`}?OoU͍W_}_~i;RO2aT+He*|!{D"<}Sx}oxDJ< f |?w?:',`/qT> ^ĕd%5/\7'(O/&pA4cy5>/*Y$=S*挂,dtD0yˊn!bQی)2IHwYnf,#U(svol)/:.vE3|삑ӳA,ͧU*F<>Di<dvvpZN @4~CIӇ^}fՌ>U2h4`HFG2JXHM)E(F:Ag .<$ Qҏo~-W)aO, pX@8b{Y(B~BTB5iؕ ;e0Ѩ"Pj]v .8Ǒg>JT%-G좋Ǖo~Ç\^pյW+t7k\4dԅ !@X<' /qddO&) f&Ecs4pGJ wd$ 6 q ĞC0G1=%%xg%tl}V EO:#7-ai$(R:z~".>$"-ik5sa*s /*$XxW Sښa¨P-|0!nHe^Y99O./N bjʀ0=;t*DF$g -hE gBM7_(-%l膝m:} ""Ej2YX ^(>Vᓩj E DuX,{Om3B}+P\Üix|84W73-^IVPaAU/ϛ>lw zvAx't=xȗ$9-e9bjU C ,v s($JJR>{,s <\X8e06 @{PVu1㘯<l,6cuXѷ+ꭷ(L1,r* Hp6vET@ r*2עT_<s;ڌ+Bgq#K):.@ Q?[͝e3ߺ3>ʧ^O?ËS8&q./쇧\п>On nl&ia:k_݋pp} ,y h3(7xֱ8 & =*E ÝO_8V5ld Sx!ama?x8^  v%X'8̙qh$ I0DǟV%ɓ%S fe hU]ޏR(/AB xEN@=j *lYYV:'s-&|(> P٢+7ńQA2'}.Q)"T~)#A(bZ7!ah(|6Uk7<:|XĴ1I;GiK(JUn G>`?ބ?1;MPN NϻlN, B: yLa\\y6h!,?{uMԜ8xΩJӟ ]wy1)^`?TTIEL(k9wTa4^@6Řv0331F^wެO,[%C)lz5>b12kdD"Ni27GʐP0je/,YX:}v8^>Wr_JH A)H47kB!1'c-*)XOdN2QH+GcB~|%z-r$7E1DϹ%u5o77Bņ-0 ,=_&\3U>aK!p'HKHXȨ" =坖#rdB1g*-)P_?X_Cr9 !VU,9UQa*UD[nv~T~~S3Hbƍ K,XcI2A$7"v`/6na6,V}R3Aw.DFgKŒǑ*[X[ 1ƴ`?j=8몪r#4,0/~rny&qžА%5]x` NfhD<$ Vp:u>}6r aoe AQ8;dUõ}@oByd*8\u6I*v{85*Qt Dj˔e.t 9e1/˺&kecBeBEM$"\,*X}=GC{l6[]0$L9@QʉKĄ)Eg~VBP8 ptci}'Bh|esxA̞8w^U-B$ |/~,`~ǘH??OQ747V4a 51WnBV`ɤ ƍb #bzs VE2r+^l7J@UR>rC' n#$иh\KSBQ@$/T'*Q|7DƼ,aЛgdz\s:;y#M̌dbv${#ʜ'qry @}Xx1RbJHZ;FVwǡBĩX(y#?+S"`#j59-;9Y{{{ݓĩ"f 'ڣJ2tr5[t/H1DdugwoգTyz^VuTUhhH`3D9Q1DIRm @rZ{1x]uHhEaX\9{RJg䌢8;UmܼaKTD[ݑbij:WB!Fʤ,GE"ң;woxQQH7m qz|o~[[V$kbI89%ǐ%c[y.KS{;O^{ŗ>9ty&lz)Wd&I dss"x2 .w"<\ElDДlײ㇥J;zC?[p?(ge^94 p[? G` 8}M8古-&8ISD~#籀xȂL1%U[u7YPFqM u}\ c9#7EjbVqZ^jO*?(TҤS13`$9cp&Pv<-i:4\_xrfR> Fn񚙉B>SWP2tdѣ|NcF=/Pb.'q<>R7L.v ]~VK1.L>|Ѷ^6 K'v6^{3Hz>GX0LyEE+)UL?bqχ_o#mY`$TufV:}k?U %0m0u-Q2`bW\Iǩ Exx~w޵i6 vN^s))M%{ֱdj(/c#cDkm`o&;]5e0@}{}Ɓ\Xf3(BnXVe27V5k8MEc(8isf/>\.OOOoyZ)Mef.܂+wUJB-^C3 $fxuCYSr*%\ӆ}3no$,r %Jll/ۓea"VwjQ_ %45!uV z}iii׿=(l},x.MU) scmT 1q $z:ݮGTS9x &x݃ږ  /22UMBo䵂"}OJNW3ۍ6:.-fO>Sapl4N_ml #lS2陹vaS.m5N7F ${Z* [D.^@jti~O 8F"kdk opfO./ EģqY8^guS.%;[ב?18dctG8 ^6Zh3?QD{sm8s|㈆#JI6G mEq* Q'UF6*w=HGQxN lrDK0Ghb>h*F2S`$k=C0dnS 2`BWݙOǁm=ʩp[~;@w]uj*R,DNTR;j?\G{edT>YZCLLĜiz_ {ƏGO"#.4.caIT)m+cA 1|TL&ªm1iT 6/Pl\I*QFQLN1XԔƆO>׺hdrwm`SׯO sfeܥ,!c:[)OA '&%޾}' 7|9^lN,Kԩwݲ~6e*Ϝ|xO.3g6;Oܽ .*3Ne]\\yz5x ̀q4p0~kwNX.dSʫ?~ D{677aich#DÑ  X Wӳy,L>,}k fBsظq!! KCGaYj}7:sc}FC? 97Oz::%|Ov\s^P↦tXE>_\\#aeK{7[L}f `vQZOT3S&b N)eV)a²Dzw֮8% ̹8Qen`NBlKʫDk'Php^`z:8]o<x0|PvE!`w;2$[yZ,セxfv~owN;DjO\-jmz18ƅ#g&.NcSaqDX`kkkFO(/؛4[Zx›o _2'?]:,`AH3O$,S4599VAWIYx^h;λE٘i<>+CO؝{}`Ӳa+ǰWJGPZz7< اG6ONVvPQ& Q\y΋y[7n޹}@ /stx i.˸N.wG]2]HTqo[N9 j8DuC#H$4 ӷF?0%Y * J֯d( "yg8}âR~~ ^`ow|>x.s˓|5jAa(jbynn#7??'?qnŋxyI/kk/^}oaaa=N KlZ#6Wvw@7n-sjsrZzcf)NQs];c2NAfƾLlLd޽v"\>7Y'xZ.|/2ܫqZ QH8\w<z&#`Yݯ ^~)r]R)1?*ia]֠ѣrs{+˘^\^ԬUs3E׀_ AgkOԤtBTFH2פϲ0)}`&NVmL\B6F~\gNQx̅"! iI{xW!.Jϟ8Nh=4ƈπա੶0D @9Y+^]oS|V*W}b@a4O=, ˜`1}nGןuШְyg4HR^*XZ rRt"&qQop3_җV7 QG0BPZb&V#2 J" `֫Tfn:n -HcPA(ߧ2GYL-e >`xZPVQןhf}̄Ag(qzB|nͷnsVY*򆦙&mXaodQ&45m3kƱ3m߉E R) wJBH^ǿTY<!SiYL8}YXm Ӵ 4{Ϥt88x!9B?&]{;@+Ӏبe&lRҖ~X,1I7?:i 3Z(nݾsRӹnRB'|Ep|syikx)QÄ@kaa{](k8%`8X[_yciITslTyit|ՙ>%yR![p}̒ՄaT; l ya7aP( On?} dL ,wؕ >tURL31J3?$Q)@ pihZR`ˎ`eVI4, }GAPj`A^ԁ,'!VlHE罴I3&8 RF{p/|(33hHbTcN5$UeLjCǷ_sGj/~؋\>Oź[ ˃0 B6hW[Ș&sr/|>^LI$ʽ Aܠ1syHmn;㍖$ggLxMJHϨƧH>G\W7ny_eqI7l% |.BIC==oms\ꏴLj{!5G6N`eY²-) Kz>ItQv-:l~1nX620yめ`Q :DkCg]u\ѨgqqJ~D+r&@5Q֨kzT_ZZu=gܾ}2eS!8}vLƹfgY>A,Kp_~ZIiYuDIDATQeM". \wwL"V,˴"P{9vZt GF7OؐM]*Traz݅ HG2`(bPG~:9VRLo~{xg~utYES+*6f2AДtDu1Pur9'_DCT=*4ijN,$_iw:\N@IlX&\(KUcgCeٟ-NYQݍ+XxM>X=Rhf.젡j׾/.I2#ȭJX%%\,NosMwoV bX"iiLWy ;YIiȫNH,5z*Lb9?t+ +=o14'c97S bz_n@`f0 @ 9gy =q7$6;;LgԛY\^_X[z]*9J"izIԖ ")YfR!GC]2+%]eXQ⩃LJpK\xRYd,DF$"6&GtZ|jB$j?tN,;GfnmwY츏}#QŐՊy$k`ʏ3P 1 0B+󫫋.{C5%QTџdv\XUg Z:( /(tP=ܘc3 -SRp8tJI$ R*:H"5SFhj  P- HV,vC<:死l]?)ڈPK+/ktQ$Q9ck)$\c!e:zi;O\} w6O'>*&!CQŒ0lрhyFp f|TR o=z־{~X}sƟL_r~NOX9;wޣ3?-=ǘik4 %m7TL8&͛vv?b[ceܳH%r|7 _zkxxY_%G^h Z̘%U?\ֵII"CVt:Gm6EXf`FOcb^9ac)q&[ʄA$@tl&yzżyx$C{WRto4[\?Mp;1I0 =Oz: I%lKU3>$Nm=PG_foSy;츿Tqqqҏ49t@43.G={>l"pJiV˓;g'թ+5Fy8s,i'h!&لc,OAdUWeҁQH%0>Rc~ߺu]ʙ!,G6UzEjA5%$Ď(|C쩨mYƎѴL}~M' lf.d˱Wh&p\:"*yÃ~tVC@jShgq Fi4&h*nFTe̝_-GTYlԫ=;(QM1B3Ỳ*`4*ۨ9HԲ `mGf[QUASsKU \OD`ʋEDQ aQ/ߣLV+yx۳YRzسf: 51 fͲNi#dXt~[(35SyTy\q:~>=*;~?\HqC~Mةj> :S3z<ɢ4yiCdB&?,fD;/Lùx|t/:L$Sɓq 7%=;r4J6p;)fSC[Cr%#|4cşIe(TM,DK*p*m~Ax,jqf$B3Νz`,IeB)L.sq a(uO2T4;Xe E!A&Q972Ty|xx8쯬A+AQn2˫dA)C-}?"d3X'NKlR; sYL23,NSa&>7gVYzJҩ*cLgUO&t|/[CO_+{9<%↣!zm}]g<~_x3,X@ݤ.n H:yTu߇m3xmcfTaOdJI{/%Is=|w0Hiӱ2$'E<<႔^RٗAjZƂ}mEhcGA<Cxo 5,Y @G>hѣ矿^*(~({%kpxC!{qm/k A.`|2K6ɸG2xC4 "?V,A,3L{0]Ta5 GQ:wHJLG1't dr# z%R;3&SE~O%L1]].VEx KY\.Tʠ90! \,/`L3X \VP@Y=d`vww0XRO=}'yg,Lz {S3KT"JЇe5LR[AfjӪi]HϋgϹ憐&R0!Lc&MO'@S: 9S@9E3z\pÏյNN2UߘTƲE?XwOjObڠ'O?4u"/7ɐ&]xwiuœ8sH; ]h'GWUvL45UMJa$yC?T$*Yk&I#ZG3r u)f7:NZw=+$,Kd, ]ۅR91R֡L6-ةz0=uŕ1 ,M9KS`Riqէn޹3N먔ņLTْ灔 @rI"J PM/ 2q t8I6?zK#(a korkO K ˆ@MRv8"ĩr%o1,U,sP|ދdSv{ͻtVtS.UǛw|d;w!g8TWY˱ P3wuŽd}3It?ÏNds64Գ≇[Nr):[ X|&*a,Ju%H#U7k33+MMG $p31j'RX;D@͉&j$jCS. p%,n`Y! $0鲪4[f ä\"/gf(LΣ|o%=}n vze~Q.`4&,blJi EUS4҄Xc)Pʋ2*@ ,eK8@Gn3~װcnE lTjyeiaiqR-*"E X<ϯ&S}姊!P<1ao6E MU[_Wy|Z_1&L5)s =ɟ ?W~'~o{?zYրqᱩNQIS @ZgTO[Nzo;N]R'+ vA'- R7$ƄR}&0&* ݻ[J`*1K%Y8;b|CXno-G-e^uٙ p#vúf}a"ɰBRCy#R oÛN:~~͡: #,.pzOLw,jՙ.E\^ω%`D!@J4b\Ae+Fe^skP՝GNWGG@ EIcUn3/Ӯ]RV\Nt 2FZ-V9q+WĕU0n8ypZ90s}ku}½h^Y(LD5;gc'x'zRhjJVDbPӦzM5|[sFԬ1>˪LJ@;G8ʂ_ly.\ұ KWV1J~d @BgX"@(58Ϙ(;$<#|;gr$dZ,IvN]=ǽ^}2g+`R:]4rP[Me8mX.s0JEʳb7K=9I 0 vjvFC4pM`N3}n~ %amfg[\Y5Kzʛo{L5Ibt WNLl;nI2)b"1;܁C/?<<`C/lvzm17U=b8> c)7Aטd2[bb(҈"߇Y+(ΫVRMY*Rx܆Gg̍|8ţ_Ǹ ;ON n /JDJ`zx,Ǟ >ޝg|J3sf$/ȏ_qs}տͿ@Cמ|O?_@7U?x/}ٜt<2Tx~Lg% ,9&jN '7v > 7?GԂ)u_I99iJ288)"Fi2,“Hp |cbC7TƽY R=fZ -9J14߿ p^39'qgyŏ.,.226.# :.$yM*i,%u6VXd;Qƶn 7$I 2Q2WįQADN\AR=ķTb"rES1t t"xF}Ws}:ܯh6$W(qM Hϳ@i`Y住𝍍 ~Ta%Rg(@7#f˛xG/R*:NXa2)MCe,z='k;S١ d<;dBxrHݐ脵b`H# 72E*3J.fJIވHDSD;ѹIN TE,%C>a@Xб,nhX1HGJ MUl>jZv>bL['AUKY.+e*&27;3sʥ݃LRzaӴs% Le,{WBQ9#RQ6a,X2&{gfqf >Î0M=1^v3bx1&OW*Črө$Iir9xLٖ{Tb*!TLIz`ȖZۓ (GKp,יS:ggi?S2?7Qqg|O|X7L.yW]LxKN+i:n.xtr~{0YXQ$8;//''؛9LĒLFŬ7z^Ԙ9x0jr}bj7/-,AՆ: |OQ })kv6@sR 7ڲhsԼ'ŞK2>}KcIgD\)DD[#Xays]ވYaTsl6$V0/F+GӬ\V#z$\$FݻD͢Yi2py{~oxen^( q b="%]sy ) "P19j:o(O=>0I< Oa]?C-WRq5r*˔qFh\jlsÇ[ۣр<0jd,:)Kz Hs`:[VwwZvHsAcqbmxQXg9F ,LW( dqrM7Z5ۨ,cIܹsRT0LQH047tv>@?\Lb@byt 2+ipwJsi4=|Tհ0$MY$Ue!<:Pµ)^ފu7vi:IT/<'SkR]eQſ=߁[쏆2 I:33dڃvuIGVيVe|w>_5 G%+Ss\g)L:R1@>O4y;ĽG׺.7X/ ؚ:vE}V{WkUVVXfwosvhk\ggW?Xʺ*H[Uk8~˳O^$Wi ݇:/2IpzB^5)X@h%֗WC"Bٽյ5x+Z6 3.`? G ( ][[o6 ϮfRX|> o|˘Maz޽`>SGG@=q*⣃=8QoM=:Ŭ 7>!a?C|,y)3Y,io/=OҸ‡SUq ;gigW?6_t|^N*ާ1q :BI17n&5w :\|D'U?QtB~h -Ml (*5U~aT_GA0Yz_~i{{/~шK?L}"VME cح׮>1Xz\qeBð~D8aYx\77(J\sxG7o.TK0_d[˫++iu{}-8m*9BÔLS\7omQ_*F;/~mrKk7~=99p^xIS5 l0qXP2kwvO.\]!6,$C>kp%#F7[ZpΝ"V]^^q&uoࢶ=<9š3<::Y6(4ݭ{zM#2wZ=HИ|7WVGLRYQw8We8!S& 9Ng6jݻ Sq3,⅋VtIf9rkO?57;47X]]4uj}n{{<&Q5,Vv,K*07%gΔ2S]1?;.R3 OENZԌp2_p2fBT]ER)|o\~?Ia{j>qw]7b h*\mx9mH"6"+F}`Gc%PҰ/Q71T*B8Zvƅ[޸y#Lb;7IlD80AAsde0: OEi;O*I-XYѹgŷo^I+++0~z/GG3ZT*8^[_riwos9'.^[^D =7Rʵkm:}?vfTt0w{x9_,vk7f+ssѨZ7J+N;^JM~]^[t} ~y~Y6A|:&?Y2BOOj<yqrTneʥƹsiO?Wnª*ݽE_Y*X~<wz)=lnB^30:_kc--,5gbU;t;.&7$a!9C\yvf}4IQbeyiww-FYeS)W57[ʘwJŏ߽HN 9N9(ҠO1`Ñ?zpK!d3W@ןn>۴lTXI(_Igٍ7v+kWRaG~o+mo\ظva9ptbU7>y9\'@LA_ټs 3snRbnੂu/JXVG-פ8J@.?jO}J3 1~v{o}v}f֘98v9_-?#!ZOhs 9xzR s,޳8=V:XǦY"Ӝcܷf(NzB$=t/}v Xd.!g-..(:"2uM^bR [_} @KKZ?PKX#L+nF}W/kr G= M:s mwX<v=;W;w՝f*\~$0S5CRN舭Z4 nѮ/['Cbʼn0SXo^v >Nèmmmk/sy[F)q~#t\w~W{p`|wä.ͩJM{owm#gfhZQ-N(&4f_{IR_xu`x'ʹ bIgXroЏ#V1p5Ml&}nEc^7\ڦnpsvwgƾ>rL}녡8?=} Ucߢ\+c=z}% p=s Fqt| Csܹ\fE3Tq<Պy88XEtjΖM0JVGdz ˚a4ZGxV 7I#M45=L;tJlګpuqN-Q >xp6ka_vLDtniE_D:.$8ڹs$>LiZI\.܂mSޠ (8(L41$:Xj  Lۖ d@RzwσaEImR4''nܖ C1C"}c:8LDYY A ˡ؎vS ZLcssxou?E4GL{ru?ר'n.\XE%d &IšY*({9e5afĥG3Z iJ il ja܃)'DtNbǔE'L I2&7>TQQ&T=cn5j *X9 2@a/ ]%Jsí e h9OFigl(<\._r8+x m'p4򷶶Xkkk@@SW.^Z?&3nDѓnO_{՗>qB`u ["vn.ǯRL2Z:U}I؝!=.51 Jv~nBEh,agr 5!ɪPk:][y1>~+|ǧ8p+&׺؃籐Nڝ4џ%6z_* RڅJFV MFPD+ Qω~BJnZ-ELD<;*Hء1UQ/lw,%뫦,B8b':t1ɘj UaᓂS y$t2V Sk4Rì ܩ0rodcc䤉6Hn ;QH=Enڤ`R/3]iKEŠ6VTgJ7v1aхe[I2RfPʳ3';z+YZzj`GswML>۔SU4Qf#LbrN~!%._xTD΅HJ[-g$sTa;tI"e D-& : e@&ptruVO^4F)&يBהECa I;53;uY1EF!S1-6JJsτNZYƞIT$-]Zd8((-iand][oVG a쥀ݢT#¡//Gl\U- }{9 0j\Y\]d!0MX_bxR yQ lv 9 MU4F%t殫xԭ:W? zR(Zg9eC SX46deXD+@<(d~_JG* ٗ!di}uɬ*u>%Z5mM?ӮcɸcxOqW>,X(a@ +zlyA֕ "3,6O\:@:\)Hvڛ>ܰzm,چ'aHO`ueA45?>6q&9I8|BCYDꙐ7=:h>}[IúxE<ΣM?*iK\SHh`4iحITŋCϙ/`Ybc=c"1E\hKyp~ Y*Z9p2e#J|@ }'*Rnt :(3VV{"%^Y$ %8I޺qM`dKoU}ymp!-Q$[ #YJ$AÎ1 H H`H$ d˲%[$C83k[ݗ|(˭{;99ߩCKEATk*OHTxخYHVLjMDVȑUTkn(drnkk?_"esƘ*DZM-=v%8=TLCASa Z  5 ;]|v},R6pcP.J @gF vxwTH#& HSU!4V.xs+P'qm}6oXYV] pTlcO8j㩬S4=xk'0|!!) 8m*LuWy}UYmQ,k&U 7™ w*qf X"S~WӤPnq޴԰.Aj):]@=Tfz $+;f$흓G͞Q(։<;զ"T.t䁗Q󄥔"t.Z#[ __7#CQ(mU+>:w{Ks'BKe+ e:qNS IIQ0s7;[6iI,镹]}k llSdjAWOQa{r1.ՁNHt=}4)QQ-K a!fѲ윐t8bI0)OV`T Nrǿ[<[r]e%ޣ'DɈ-_jV|Y/iϋ)T]i4j~|_1p/ۿo(WKt. Nn0NX(^5NŹi?; Eycd`E";*ܒi;h|[tyv~T($ssus#,C%BaTł6! $JD4RaU9Js84[^z`ɴe6B423R˦ 1L! U7O" %Kn 2BKTj*3(f 0e]rX/:URW[򹗮_~l n/&p C1tFNN33@jVg @&vPƲ|U./tPc!7$PF;6TQ=HP<**RVYEEOByޜec0_>DV?8KʼnlZq:7SqL^F;m.򛧯Ibt-gQ/w88:T>ygbI ks `tv;)dIP$p}@^uUʘD6ONh LH$lޓݽ}|_vϤǔ&iMj>%FAq+:~x9dڏ_7Ӓ鴍!j?Cקͷwỿ7A;au6*| ܠTx?gǭ7_|._ΛCTȣ bBF}&jr]fgR%Х4uQN e9QBH䌢 c"# ؄lg7S@!uZ`eAbT$_X.JH 1d4R3J1‚.:эG:%Ϊ; G]#N L:q"Y`ts <!_Hz$4"he0)bᓧa;;q$u+uҢ)TGǃ[7~~Ӝϧ.شcڃ'==‹ c:f?F<ׯzյs+0LՉt$= ESUjQb;N?g  UQsbnPg-y q4IH cMpEWYR] Ϝyqa@<>۹2Xj0iR^AyC Eej X x+ {^ӡΣi@![*)zXWܑa $N@b=Թ`,GO*X?5#6}U*EtB꧁ؿhhufv &V)) 8tD-MnR1Ǟy97?) zpsGCPC!af% O.FanHzljqUo2 \?i<~zwqŀpv2-yfpB{m40JXָnµ6FP$8 NBҬo0wa@,v ^iqjFC/T (&Y LDe:*KC YX݀K! WE\y4>w55瓬oʡgR{z_5q#}:ep-t6ο<7trvii_8PNTӳĄхaDTal$^K1i=㪽Pr,E%HH%r3%'Ƣ EcxלgS16&8AõPǾd6g VE|mZ|~f>y,oY ܑȔX4Ƴ+ؾHr/͢9pw?uFivnXju3n͗Jãc[Skɦ}ZlwmPGȌU ;^2Yʸ3cH wO""P TYYx53$n~_Ys(?rqOLwQ*/%&67=|)&.C>Vz.uyR19G"t+Aɽfyq~]e;vrp3*\c1x۹sɋt4*4iN ?.gSOU_H7O6'S5i)hb?O9Rqz׼3˟jgŀ@OʌR|"#L؃r>k ?04UVM,ҴFIRQ [sz_th,HȩW##\#b€Hld*P98້Yfvvfuuuii,t`\\'%2ZX 9 (62UCߤ%}e9Cw:HtcIt*8.o›s$CTqjaՙ⅋" GY?.xjIW˜&p@QroBUǮ$ck~ɣl-ѨKaZi oulA6| mv hIԼ,4?o)+%[ʣÖ2k-+0s :\f]A JA(P_gd~pͶݑNuhN٦3{}~~nbHI>ɴr YIu'-nPܝ7>gs%_si@3~ 4ri-`5+ *ah2a -A'}`&}g{}|?/,O$c^0Kq3d,M{ƜŰ0q ^i sdP]%i' ֵK I0,Pogk\S lzr8nWҩ<;jؼom\>bBU: 7qCe?L 4PSTbWU< LNj$[ hv?iO)hY?h߃M:w64m4VހoMOAGCK $fZAfJʺd$&'E/u$N8;~YࢰTRjt4%6ҾJ}獫q XۣbT V!PYfٱīv07YpDG\-g:d06%AW!ӕPJǑ'QF@{ +iqSjvDMC$3"zڠ ޘV*h`W/\:Z~7FqUɩԞקj4j5_Z:U]ز-RMZRssXC㚪A& K9W_,]AXRlXL}RЀ;1Ӫa ۛ[\F~liP.`U\P.oE|6L@Z. OÝ(>t"~* e&i+K3\.3ss*6a_4:奥\Mȋ牰D0c/S ? @Q=>-X%s:S7^ Hdz(b}lMҐ2s`  V-J\*1VZF;yXcM=uLnU_?{:#@#-ojF!c[EʵfDrv%uI |H7(ۤͥx1e/S'c 1~nesʵy[-s Guq$勱E]*2Ej( qC0tRVFADiڣ? 0r> ˱3RKa)n3Jo+.Ư8sQw0 Qz>]'m[8J5DUJ Ț5›ԌZ?:9M~ghg2NN=#&'D'3҅ w(U𵋗>{ha +I)W)@ȃ))d˙I0rK׶=GW;sFLY8;dg4@(7>~F<^XY^.Ν_ \w.Z07WpEԕGONL)zycJ`|S$dz+8qƟmmwLWT)Y1Y]v1\eB[DS#pа'[ϞolnH~.z q?^~k?~tx߰UC's\:^b_)i;t}7_*5=K61f WZlCMd'4e7;EO ,֥7e,G-@<|{=x4:zr˒?(@E o{O.RCGc߅{[23-멢5Zg ,Ǭ:~^yev$Hy8W_Z\d٧ʔ9H a$E9Me參Oy~q租.šŠj ?tO]ٚf[;;}'5^? FrdgA_Vv1uhJШџ z,?-F?~(ce ntGjug{+;'(/JNLRg\\.΍whJ#IY|zw2mGia~u8 sr謔zcH_pSymTv%cXaXt}㫡;=Ʀ큁{Aoa:_Mƽd<,.,Ѿ_SPcZ>{7;S(2YQ+AF1DJJv,Ͽ[˽Vsg{O`;i\-IYfGϖRx9^ѐZYyX{ʥLJͶ7($RZqJ{wt bbr*bywL~ 8/d|kJ«*)HEMn/7= L2O֛n˴ v jiMGV,{{_l. fOtI4uUẈ?6tOH,/R@)](di7n޻N_%MGy1g?{o'~X,*CJ|/R~Q o~i^קjzP;s q.cJꫯft/F{ dL wr;]^Z?~MmQ%XHC: 0*9& zFV)S=-ƇZva+͓*|>Naf~۟~Seۧivfse拟EfkRyϞ>sy?Jat 'm1<8ƸP@:Vy^_y&8Eg+ v!^s>IյzV; }B0gTCU)RF/]yR?fсf ç޸<9 { y`3*ܳsfV?~xn9&o/,.ܻs53z$|vޭni=|qݸYÇJX eJPo|ao4onFI駟?=wV?|yzEOgs`<%ryiq,,.^~xtj Ӓ]`p$$dDxRwKaw'Nt]M(RL)j\)tNq )wzA+8A޶Ŝ:n}k_G㣓LV)-/-``]V`;Xt~?z-rlkf-0RDp_;^T[[W.l*k7[G٭Ã@)bCxJJKnzn>VVVI188S*`\0<8:]\\\ZYyQ.%e{{kʽw&`ec5+˽w@%zQ~q_|kˏ=t[I8K3[[h䋅!k7y~p0\>Qdsoo_Օݽu,#fstz$IssCX.f6{K {ϯ. 5GN¥yl|s')`@hO?j.eN[JJisB`Kv:=`ɓ Cg/wwvA\}|{R\+/V[3n Buꍙ9:tK(Y&c;a|>~g< aurncx}LV(@)*7o q8$3铄MuƔ8n dYX#3$4CC4MSx1W2-P=Ԕ IN΄]iX4???K so]tЇ y ˋY?>lG zj޺qRi&_w>CƥKDQj{G'^obL{]h{Ͻ hTf kk"ɇsvxKO>?96K/\{%8뇥|ho`Qv#JtEJJeuIڱ JQ?X'''@byag-\VK%E?zqds'nVhW|1n~{w.q .+̓#1JF%; ]BQw;'ϭbq}.?7;70|'cl<#vOՕ$m=K%AWԛvX,0ZP2Lp8 l*ޝw77:v{V}YV[zK3fv ;o0a>o4btbYv otӌǻ[_9i4;m4דAkjny-%:\RF>!aXug^`Zx 7H$/L;>Xj7}}NӁ{AvH>Oi$V+ׯ_?|OO2o.f-תsѰ2rY'N& YM(Eؿ|yv z;0-D}Xg<,Jsp0*K\'6 %zLgiijaaf7d*ՍUbp x7ud",L ˘LT@r\ooBjd!QjF>V>9nsy> Ptvm[j9js 8E}H^iŸ5|2ywŒ>{lmumq~!LFdfTʕR"jxE 0 ೇ?_yn|}{wKYGW`(;w!#'V^hST*W:>ܽGg՗7G Ggl-mo]/ᣵgۉ*k ~h[kWn~S&.l/f} 'ap妧h0h ž&F! q*@8NQz\wOΊa`, \d{w3x;gAH: xhv:)BC\o Nmjzpzʋ/po|Um+K1̏jUzA׻k 66/IEc !r7M|5#@sW6 㓓k׮jdeeQ/;`/ŮwZl6e`Ib_K_zp㎽RTs^HI?sv~쓨E+J,fAm(H]T5"VA#/-Q% $F0цlwazTo.*Ө1^oV(|.ToMdMB\*"xPavvw%ERt^ЃѨmn?W3k&R9:* M8]3քDD %y4P*q0Nkƺ>ea@U"?snJν +玞d3b%)H6180Lc:˹ %w1J,z;Bg`1XA1:X'h9%cNJ?zz>?%,--ίW-W,a؉\b:W^{yp4 i sJÇ/2|^;( % aXX++xャBцQ)pJe+J5ҎnH 'QZW鍨Z:̓7_̂?,__8_*bUrr$L0cS3juwwf 2uvASFu] 뇑Ox4Ȑ@]*xM./.~M=,B!&??SkZ|zX릑AxҩSv۷OFG7S'E'e̟?!ZevvZ2)K&;d~nRk6N`Syx]ԋbxΘ- `J$ppL˞ٖx 9*6 4Nŧe}y4-W?T}+c>k4DOG'M*Ԧ2/e#GR դ լ kU greym¨lR#:?CUx ֙;K,˛lf3 %JYؗA`*J( }ɕjJ#cVٚ H-y`"Ț)Z{Y[S88<>[]=eÍ%ųbUu)2.D,I~[oZwkR*"BQ67cawϒ ?)!!HΎ~nIJe_ jhO$YJ z"+3]Di,^'l: /A\d%eمJ6_Θ&!y ψ/v~'JrI3+kܟƂTΙWrMƲi b(+ %%%@^(U!֭~we<[-+t?RQUE[ׇ}W(̓k"x Y PҥԫK%8IzGБ O<͍幂Pa)̔d%0KrC#1 ΨXPذVA1e#+(])./XlXo0ĺ=ZO:iz8̮d0p4Q*b´ڳe0]yˆ$~wh`Iâ>Ӏbgh]үn^T繒L#6S?WGԹ9/ լdsw˓S2ɗ|TtT|A"}ޝUG0aB4GU&Ɂ!/Z%Y{- doj[JW2ϕW" zδmVΨ(}>vVzDnJGA҃ WjH1W.T}2Fjn4l/VrzIH-$9;TʀQP{Y |c1[\\ehA2hRmyMقoV J|$bEuTzԩp|Lr6bH*D/Śv}0 C?7GQҐAY=,9 AQ`' b h?~:H%L^U.Ǖy`t@b(iJvM>y2zea2=g:9asGC?88-Ԛ̈e̎b΃vƄeXF>d"&= ؇4 Q{'7^+_T%/ xBGEFSH0-I>cfjWJՅrL.孜mZ" 1_"!r$Ssyuq wgcFH@ڀlT*eC`''㓣;o᷿m|Yn^OW=YEQo`0\)FAb~ueUc-M& Tu}PE4= w}7'F&5Iu+̬~d>2#}Hq#[/zTN*$iB><\yk!aU;k ElXrt#m)$3D(Un,#%9Mt* 5SGDŽwL䊄 2,hpUZ+/ Jr^9z&JbBY+EWE#ʘ}.eAz~ C%:H'``bRtE)!(bjm26z;%>:}8aT@$*ro<[3soܞ(nKʒ&!"pHk ÓLU i뚪;z|zȱgFlnYe0Gw0D:4b2I{_:.N =TxT՚ޗvO oZxMxKN܃uCaB$qM*J$zG;;۷^c=E_ I]d\$P گŔ"p}#<6qT?SNqM~4N">;oIJ ~nE2-.ē鈳HRٱdTT\~$\3v?3k&βWuR,bAtxtx}/?fnUL6keL֯kN@b|`NNSkζwI %a:HQi_;Ϭ΀&^xJ_?~RS~%T1|~ӧJfϰA.i WWL$ABŐ.6x E,8%yðqTR/ :)HI`8dJ.-̚1XEU4y9PT&\*P-&l;vt[ܼr~MSL 2.nu]\b6UwENLrY4=!Gci0]][7טZ}3HsXd* ] C QMy33DS?ܫNWf9C o:sd]^^sYyطo7^ukZ{gzf螝 PX`X&$Bp8dl$a, F% ^齻.ư7o̪vWU֭_yyyyk˱'Wr=Qplv\!_@MH\ϓYgx%OQ{#ǭ@?|#iU,a$fwl]kɯ+?OsvRLΠ3R~VOg4b:=7wa6SW/n:?Ud5^*9I(fO۽]MO;*ig\|{k]LJ{ҁ rXG1r<9رUff2-M9!@A,=!We0|r^/eᬄ!'q 8('uP jx3}Ifڭ;\h4b}szpOd)ܚĬn0zR"!k⮔񘞗_)0GAw!jX*85I/%Ҩ#RamWɒqX T4A f/&S1fMZGGoW/[x=[2ƚ\ZѪkSO (Z,k45s4-(33;\mqJ4sQ=16T"qS#Y_[[Ýe? m1덈H/ }`w>ݡI 愄<]hpA?nwNLJ~{w;>n"łSj 961ib,y?։dY.BAX^YYY[yhfL,3Tq~L_߯j0Uխ-Y$CK& A/GR_>RP)Z(y2Nڈj>Gw=L蝷}FZӀh8M+dHB Q!KadJ3l]g|0-??P\1 XNdjB?(D.׉5^*g+p Dg5ˊPVy䔺:+wx_w2 =weԨ!~`Y\a l.@Rq Fgۢ㈧DC*(SD)ء7kTR_8ks(M W\YXZ+ .X^V1Vwx2tmjXgl&5a9 U 5P17D{2=޽CqTPdA,>+c F$`PI81Wd>ȷ۠N`w_,,YL FVTbI1AUƓe"uEs LQvܯ$M%e~],´GNxuytcku,|P$f(dդWg)2MˤIG~a0!)2[{ɸ K?`AV%^F"gIeV/,\)W0w:pPaAe^2S=˕,d<ڶL)Wd] o!i* y$Er-Z(xlj!q"IZa;Qn.3/hxoDh}{! o8QB9! ڳf\,A$jhb*~|sr0?Waw9.@0@HI_"LM;Ԃ{'u&EIx'°OUbCw "}v ZB)q; S+avX֔MJ:HaI3ƏqjwU)ro8R)IedIT(M+y|SAN}wj6'aFϤ:dZ(`>R-ZDR6tB{yQ͙&Ĭwrڟ}3 4T'2>=EG~G߼uΝ^ŢK,JH)}N!yۏ6j$vhŦ l-D$D,'mgyKKcoZ4^-QrQƇ@DK&测Mx=r=@ { uzU&aqh"wC.I/SP|zbX`+j&'loRN;HxLVQqԔF@"NCHկh-59-TTWMj9".;b%/&lYbűpO! Uud/E E2tN}bpDYԔcbba4wVWlw]?fǚ#~XI$g.zeBuG^AlG QU䛬~@u-.-=u[f$MBDeZ:4c +ic(k3{%\CTqaA> 'TabO20 [Lǘ씈"M^5FkS{=K3bojкtĂZiP /h.>w'{x}?޾hMlA5l^0QsrRF)+XFUF䶑!}TԥC/ u_SvÇ;xNgpEǶe++>LXCR妠X5CgL&G'S1J&wn~;~X zc8El' =jXsDV1]*l0 82&þHٓWmQeg qf؇ %2ה7B(d"{$qz%H)ʧ 2/6 9ÓVPeX~QM!>X"7Q7k޴7!/n⟦BRf!@f} '6z(d /${m=."'7 Gh džg 37^rNכȱ|rBN >C"6{rƋGG͓+7MQ1Ink1 <2PQAx oSNdg4;썏;HÅ^+N5{])Aj86]=xRF :{nHZ7R.d20H*X>0̉wf Ct@|wkϒ0{yo4&;o˩p/BCqD`gpIhԩXkLAd3EKG=\t{psvl-//mnnfKc?jsBn Q#Mr.rqxq7|sx/j0ԧ>Iyq1;wYqeRV^$Օԉl\P*eS9J^bus,rswO7J`[ G#%d~zʻKXgK4 Y0:+ m%!NlLEMGF1%qTVw@xX Jj"E̓QԦ2NS% ;$FLMؼ@BKm4!^z>gft- Ry#r׉[H.Q}Kآt^XcN*`Dp~ w w4*-F^KFe' k uU&E旿ͻ`f}9Aw&DB2vp`ss+t^MTBy< @$!)A9h쭭nn?LK@x4=r|E?Q5)FR͊̏SYU@x) -RХמ ,RWY/D#?0sMqjCHWJӾ}v{R,qH^`(6bR)9ܵnxxPffFRཻnVwv#+ SV#h8#/<лjϵgoܻݣ!S4tD1Jm"h)+qwR]15hY :^{pb({bBz.KUCUv$Z>8R*o/͇͌ӧmЏh77ZaTA'3Ω7d83NC'^~[ ĐWʥXn\wmw{p MHv8D4RCsb*SE++u ¶]˫ܽn׿{̓Ogz4BB2.0|&)nRe /f2Y,R k TEÎI3E!iW9Cwv:Þfe9|.5T6R [w{ZOጛMdO `sխl\E{{o9ԓO:uZߊ r)Z\a>'Z݌<0[r(N)K:B~momR Q/i'ٳgYgT^g; 3 Ps ȣI?$6Rnu0mO&AyV!yӟQ usg{عWޞ E Q(T} ^]6e&迨zkWj~ K|޽3g7~ z/ BZ=9aҍ ۷oUnfS{>B~V;:< ҥSNax*.F08v`} > ZCJ|_պiGQ$$gΜq}XDfK[ >rۊY`*At(j}Aয়~&']UgB h2>Of/)Yqiw"H9 r?>y g7+p!-Ė}D̩e6$(Ib!ip ԏ+0lk3(_.ۈ`wR_ IVŚ* \u0NW.nݺ=ϟedհ˸;e)2ltƒ{x.>f"^Fh#/5Q )r>l'wŻl ?,v.1 ˵o6Μ-,,r޹q"H~@q$(Nakiq7 4 "Ҳ֘"b34=<./<]1;?z@$9s"Rvs~;"(fp7kaR(<xYplpYg'7oxğ|1lݞQ9JX%^T~1^\ҟ8wvZoy≋ӧ2Y[_Kg>iw"јk9AX(y(%aw=}YIbcי&ӬPv?:q|d#.v{v^Fc%pC&L&Ì[<::ZZZ3 =\iuE~[JDe49ȟ}B> xu||0?>nkGKӑ4&}IGx>(ІXENKK ~+1bqS}9 HO&6QŒqV"6I;At!C |/9u ExyuF4QFc! ?h<^Y_Gu4"ʜʈUGى^#`8X" Mb-0Ȍ굫$ 04M6wuee8ZCXHf`_Q {la-Gz]畗P.W~ΟpE[fgQE`H՚au t68yϿvBFzm`Uo7+ pĶn--T^}5zZBjgh@a><tz&mg. /\8>iG`?><ʵEDxlf BlXD?^;<88ؾ_[s~MP{76<ԓdDsєrw<.?w҅sb^ה^CD u, &[ܹswqi ].jutԄo+!m!J;:`.9 i>ܼ큛TY"1?/ΩX\v +!`b;w\ex\\)ʈt'=xOrT-S4iq!Нr5_,Q߾qW^?q]xՕՏ>Ȩ ]/t3/j$|4ͶRg?kX2O9};O}-QQQOKHsg{MSrg'd@~e{ȓ¹Kk@ba^X\'NOQmۊPw_z8H HBq-~ȉ0܋uԹffL1}>|+W򫯾JN;<ܳV~'M P͛79Yox&,spmnŃ0 Ń00""iC{饗pwMk u>ic)zsnjSgg"z1-sx|Raqigsk^njLKLT$>6KN @Õw?”:5'#NJⰚ"a&{T][ݸ|[sz!j^,woŧի v+2ª<>x$qᗱWULk 3TU>s}ꩧ&>p\Ʋ`>DQS2G}k,DvՌhy@.F`NudI(GLإb婧 "=K&q33`G͗?+H{gNڒ*Ԃ(6X RHX8vt%I22ٝvRACMvƂfKcv?7V ݣfT6Iob([x8),Ms–e>ǏH52RkXy\ta?4蹃R![]\ryI4NtӀw`Rϑ>D b{pSO> 099IrQUiPwn^_K.g#)=Y?[nd{5[G/|93g7=8IT>OZZY8,X=KXBC(oΟC*!(5qCc+b5"B]g\3qRY=;k(nmmm!؝:ؐ4aGme 7n]]X"ŋ|>XE׾ӧv5:}|eĝť:K# 4JRSW9yYΠc(Mϣ_2'e_4Ͽ2:K'}FXݓSaֲ?f5A: k1*VV]CEZ]5{W6ԫ&a v$rxړfl~i鵃$Pͬ@ ;KjV˸xrHYӭb# NHB\`=|'<{,>>ќʜ o|>egg;3~\ g %s*B}CRB7o߂3Gp8uԩͭi卵O>!b.yRn]v5]X:՞Xk98ȍ0GN E!͍Pb$  qieUݏr}AI)W6EI4|MS{B2wTYs& T>b ]@81ʔ30Ej>yK#H(<ZmT4ыǧ>Xo7[K嚠'#gDFut%EmKqQ"ΤXTC#_<8Rkf1qzcRn<;s{C2k+J*"H+#W&8 /M~~?S0Y:nu(dk@/*)B8D`G$X O d O\t.3t`G3e68?" M3v^# _[Sq/Ӡl'rd3D~_l$֑c8"g=8w`e*e=и\_4|u/PRʦi'2 9_u?S߽;vlZYZlXha|3=4vw*oZSgt-ՋvweuTNݞ\9}ҥwy{?7n@bNFQ(9ߨ={S}׍ŕ?)ɞdh(=l4R "%T+ KKK7n8$(7A.qrt@rpѴ l{kX\uDiT壣V7j {h9M|=]jZHq^eUR5uL?ł0Hw#Eˍ?'7͌^-}>]aX.N@u,U![(n%Și|כ,vOc[HJrhV~l;0r{ϯ,ֲo (|n~GEI4'dJX﹐颬dMT_R։g<hw oxv$b>KpxP Zt];bK>XkNo :ΰQD1[%R*BJ{K,Z CzbA1 DSKg.Si]\\(^_(U 1z.VO ґ<ëcZ"~ƾZ9Ekiˠ.+H&P&|Z)UrY{8 %\vKGtYJb*IP!b~ LT493qPfƒt5Ig NYgx wRӍ/_Yk腳R: >.FxY |i3nsRsi!3@Z-xaf1uN)m( osQ+cx5"5fyeLFFeXJ9V?T)ygW,-#1_&KyRg8M<ݜ &nhh*k2*3'5fI9.!*.NݸuB-W՟&uHA#Cȝ˼XgzsA4e >t:ݺUN"_x .H!GQ4O;sx܂cQ"nYjv|&F{# 7?ԺHQ"&NlZF/4U5ML`{n4,Cq+`HJue~nZ_-77]۹vźʑXER$g|M"4q&DZŤdOs/}=1Ռ$(Teu '!s4wwg7p b3Nxxb3HvDMsPiI I驤=g owx-Y>hVi֧'S}gPjόIqτާ_A ar>ެvBCgR;gprXXY^6Rb%Y4?uL (Wxֽ(6ʙ%U-ۮWTevE.,௄GR!L%3HVJEDi<i'l^\T Y<N$}?y]wvnu=7PIU%3Qs<`1W x)2t4$y:iv<3vn7b+ɷad1wD? ٢n[($\ T]ƋMyg/yO2嗟+g25}LduS 2ݘQq)3IDj(워%Ns&l^w2g.Q]1BWx#\3fXlI4ZQW1YAڃ1(3K}̡;4.oԅXhc/4'ui"4`,w,"<_lÁ&ZذD> P aX=䓗*Kpl`Qu yx}w$Xj>N0K:cjǁ"K: HD@mK]8n{Nk.7n!1RAG [L%9i!).- Id2JWqc;Ģvl&Sx 4"1`eNB'ⓟ膁AIxxjdS2dX kpܖ>Qnd rƃ8xHD'‹(F84t)OFFo޸pE.KnKN0HBTI؄¾OTQ@tRD@Vo]æWPUd~JBFİ-"+$uJp pn*cttHJ50!P$j4•jn4/6p" B] f9x~jԥ5 8콄l ЭW~3\(ҌYa>HΘW3/M2CaFDyMOgCZ2ЏIdҊQa9{IJtAjkAeEp();S7IĄhZd:0#ʩoa{B)t:D*b&jVM=tuI'g֗e#7؁ bb^84)c#X[iDh;Er\)e$>;tue _'iUc+&;YCK*"/ L ֩$P8v5`$Ž$ѩKw︇ٗyiuΩ)@V^4K`<@eH?r'N*4y5apDSeRNKKhNR{{sJ K"?1MT 1Tb9$0!.rcEGg$jꥅz%'↫>v0 jy\l-P$ OรP*b5ӢM/2㧎\+!n14=M3[w %T~fpN `SRn,-.8Whdƕ^rhLM211 ,Q=5pP#|3ETyUء==ߜ-fyLd "䖚N|GUɾkOD;fT841%MGB!2؉5 'SЅ|$Jw3)!\UnoϜTt*)OS/cNUPY[92}W9/w^!zs~J@3/'_`;>7y1COWx9w#O[CD58%H_xa)QCP4LA9_(>\+!e¿2(&dY㮖=jwGn,Q?q#/,4 6f\}:@K2]1M^/5(/x;Xj0ިd؁/7b LJ- Ҕ'V] ;iI4?{t4 E3!ΉL˩)& MRJa:Qs g{Ao25 <13>qE6iZc$+0u\TMfd!ז/^ ; Pg5[7?{wﶚ-qį*..4(.4q:v+-bMRVp{L\X ^{Ǥ셹\^xg6q9;d2;g&Jz[ =U/l7ILo[P0!,//5 "ff?ŨsԹ)0#T5؈zQF?t%rH^k4VGA"r&UJX8sUi>;?'Ο#i<%߈SyLPQ~i8KL"7m‘E$u&|UΚ70^cF=qI^[7n~f;-yy )t]1!n8r-{5,0.pdNQtĕSN_|nԪUߎc< {Ivp1d hKfvhM$~|o|N1,߶^/IYw DN*QDԵ'd %jĞCXW>$~)ьS!R e&C5_ďdQe8#Z4cX(MK#aFJVjo{X8'Sn޽7I G'uUAI2D~Ag(8N_+lcgrYFh"PqF67ul9^-y(d c9E7$Ŷ1`y4z~e2T)d2ĝ:SIY; hgD[sf v>\ '>޽Z,--ì)3K0W^o)`VD"1/>>kx 3i< T_ap kW.?sq)q 4 i&+ "1//ˏ}8E~wSb0Fb@yecSY\$ #t<;0 qy/LPf/pwJ2ύ7W_{܆a.fJSaagJV@݅J)G9j6z(/ Q* 0+IYC ɮ^cr(ͅm&nUIL է^z * r !IDD83cM');i @|Ėl}}Ӏ7OF,~2D;9>n9:A3B cM/-591aHT fX y,um{ODٱF2._34dD/35NYJG' \}Q630y,_8wړOU*g}Km1[ &0T٤E*b*ҩ5iĞL:5_:uo\?nG{.{.8`:"Ѝx 0 GteoL"Y0Zǭ]4#ZEc᱈< w$'?<՟s1&*kIB;FdlO̕T/1w:7-l1wʡLiYzɏNf`Ҍ~Ͱ#|L/`sd8ܙy"ƹp/89wH~ Y]Y~t儤rto] y8;}4.IGF@kొ{N6̽6sPѭ:nG~0, oݺx~8hɐ=.xߝ<0UΓ)nCD#Tܗ%yG1i$ɩpwga)~ŧk1 LJR+`5QdR}G&y/$"n,+"| `#!b.uۭw=)>ɟ$6;9hM+PoDH?xŨTL^Wo5qbd/N<$Ew[չw%VsrF/ -$aNJLU]?DbNQDtcp6HB`{7D=%DGZ >T㟉4Co&^KhKD(E\O5?$vC$ Ttw> $ HD5L#;tGn$-1)Ũn$8 $$7efs1JlrXyKbY'<2m>%_*Xzŗ KBHFԐax[YPe]g"% wp"%.B!va-z>ƌNFDD{.XH<}7gP~gnW&qi,CxؼSj k~9Ͱm~dƋ/P(j143Z̳9q ի/vH$=*Sy݄GWa@og)մɇ2'"&gJk$R0w?1#$X(~!2P[qTcqc)%eF~jG%ɣ"Q\>5,T duZzi`$A\ [4)9 &e ZöhIia*!YeZI̐$+0x0KLOOU]kV߿}/3Qh d{CXcsXTJܔ87$̟QXխ %}(P 1Rq+!sjyB%R"IHѴ*Z)L*BYi"hLiYkF4*hcSd߉bo Y+R +I!Q]fyM8.P'ڸ%B/@48 b$y`ȳB>4ӱ:aЗ'UV yxx8 ߻[4JЫςY&_%txnQiaiV]FZ,}&z4g)=HU}_HRWпX&2zN)Y$|ՀR/gg4h-L,5Fw9/RQ<LH n2EN% ^veiC zܰRCOket;b*IspA$Tb&#n!əF)IJ13u%"&Š}l6cM"#pGVpueOOإvHZ~ !V`OT6JIb8wN;ȳM=/@NؖM=n"Zf@2D< 8d"b!֫"Qm4sZ'T # *Id % *eӼ Ap0MFg4Hb6L+"V W7,2G1J0U2ve`k8 X1tQ+3/`<9g[[`l`w/jZxiT1IȪ(A@@ ^?" ANfTtm}B Ar'$<kE vCݾiYem3@z||0(FY%3Or7S(*g"Tߋ4_8fzPbz; " -jK-͢']K@C|01HsJywV0Twp.W]Dj8nّr luj!kX&$ψrjhVAF(&Ѻq3˰ vrHЉC cU$ W6Hnƨ"i?zfA$Y2l`DIx< r Ao|_,]~qg_{qhbYIG)_d*/PH;֚n*"lȎE;mĬbJΉ^kRUJV{m,OEn}Ϋe!T`SjGCrz=գtrMӌr4؁ٛBtVs8 DGDa{Α CjYJ Sz\60pƓ"4Q4eqj(b$^pЗ)rӒtޞg{=S&O|L0a3LFa2Vh$t1sX%>l1V0Q8!c dP4&/z,I/vYI/Tjqͬ,p,.HI㔶;f͇T=/I# /6,5sk}{*Rkcps[73Vkx3hnҨ«7~m sO= jY޿Ob!r-vY~+* T ^$)JY PH؂(an h;3GgF/|Zj;j31 Mo֝{MO\6}rQsiD ݆QL:9nGmJ6v[պd8m53a#IA85ܢyTn(;|' Ke l4ozGȒf&(\ޞ8la'ȇ?eXX8,p푰$m,iut6c¼&̵'aZsnTt:aƂWl>_:LCxQ!bqf ѣ vl*VܹsL{2_OpX .Um6fVcױ VAXؔP)̚/\_xK$.UH!̙2sz"*oM;FfSVd0]-%edg?%kw/gךv:Sh[=͍D ˴=2)8<::~pt|*!1Dmnj<әmۄ(%50#`Z:3۝˗>u;o}hպxNW+͖!%kNO٤l|h /\oaϿ[ vpȷֹv<j- C(wozW]o[sӳ /~68hȆ5o><8C@>z{~;m߁w?8<<==KYΉp3RseI T>|#ju?11 @4E4Gz ufGE@ù1ģݗcŨ7Z,$~ᇓ۟qRΪYV+)/s|2>(ߠK&\̹_{2A~YU}A`"[HSxR¿!PUBR^ﺰ@0, X}_K6 *Г$d*D"g2pjگ__‹nE TodND0P2xdJ [ C8MQ b\;<8Gh8PǮe*iM|ݬ}\7 =yNas55lH4 XuwߒGmhԄEMME¤iV=<,8d)Ĩ??{_$G{T& !?&n_v5;|{yNnQ_w5v߇uת Ä0 EսxDh!\`XmػwNQ$uvv 6}2)M|l8k[^ϔLc𤯤Ұ= _%zi<&*>瞻?G݆[2-ErUs{e:>zN\u3܄+V_ !I8NeN$/j nb*wG뿁,IV ͮ<`zTgxJ<;m:I ""g^~ 8d2K*I_ꝜIr;P(`}(VU4y:rE8?޽QUwm}ck{:%!r~^R>>8:>:-6$)@E6.Vep~+gKMD/΢ZNExFRbwє09A  Y俋>)!88~<P c1Xo_~ONaY@뀄:Z ?{?\Ī=o'zKYK`4W\ixB rݘlFx`<Lf%>cq!٨{x7OMG'\7MKecsR̸p Dj!ߵ]2JI׃. 3Hז--bINu;k\=WTVoW6u5(Gw.Y"n*+j$$>C@ΰ-& bkxxr6lҗ.]99:R/U0l2U V@̞m?o[[iأ^D6a?Zn :DpVJeU?z|6s|rLF&`d+˹݇ullp>V|ϼk6 R><[nO.\@Gq;;;7Şۙ?QTLCp)gS^u.< R ;;aU>=]O^<2vck]'O>zN @eV޽$v9/:j%[6K%ՙt Vp2/+%Og#x# Hx~ @s|ӻ>o zFk! !@rb1u) T>'K0gvHenҞ]KWzuM~/?޽'B>؋ިYη޼?-W*o~k]ƺ;GnmaD?N]DYv~{oux oG{Aݷ߆ˬתpb[?~nzR5!`wWJQ?5-T%s Ȼ KP&N$Mp' t.]"JyJG(Lӂ]:.+_Ir /"{nxTQ"IӁ{l4k$M_ Ul8-`E&l d{{H,w%ݭWkn[{W(Y?$絲% kk1;a^H2L^٬Q-^dL鹵qKF9ϤF+t{slUU3?La᪵{;f&ME^ >*gR ]go ;=Ud 5Eጧ3P4- DV~(ΓÓCt]HC'jYo e]{Wkgg>xuƗ.8_ou5hޯZKwtQE_ͦs,IRo4>7{gjdi^/jZ7bxneok/ NOo'?Ux|=骺}n>hm}}E,"'NY.?]rU<M,2wac\8O0Yu/W_YJ.ĿfEhx-͏$, YJ“vAS$lz\ʃ.\2b-@y+}.aqo߹xU9N8>9?D'[m)Bq=XfUT-E٣tr37@$ O9"@7^CXzɺ)sãW.\_{;oԵ0_fa`2*>(fDq"ma ʏ j'Zd๲j(f9 /eF{NyacX )qxq)>oVYr,|Ij sSt .2GI/`:$قID#`O:,q\Og+զ9)iQ JwMX`*!@Ȇ:x2](sgݚ9/YbpUs|o'(%30=!IvxȊ8"I'U*xAEXMMO'''DȬp>RTs'o¤,.J;TiziY`*"[2dyV IV|3% 2% J֫ :dؿvt֚_K!׻:F\f{zCEYA*ӂWOfݵNZ96"Ŋe ?9?kxCT$Vr0q)+ݺT"ٴl+(=+|ZyϋJ\UR N7keJ,s%WVIDAT_~ytr <* V p2lq ęؓ}VTΕ|ͯAvB F'llzUo_+mAWTp"c %}rzX*l x:*<}VU\rKZ7gj·ˆ#"e/)YcL,"83CV"r"*wޖ ͅs{fC¸s:I@yZYqIЗoJT3*r"#$S7NsI!p&kWʗu˺ b,C]-b_fի2ET*9Rbi`T8 Ե}pߊFN !gNN #hA ڞkiZ VƓ&% @9< I)c8 ;$=r‹/}o^tfQd5(-2؝l5[uJ$3*Zggg"(tSWAl>h 7 iOLq]UfvɎȺx43x,xw[]Z0!Pc*J ͒I HgqׯJ 暪v9mLF!ojɰK؃ӣIF `4Nza4w2xVJz 󴍘Զ"8ʒtm6pZB7zk|p+[eHNْT̃ Y=y“woI Y 7J>=;t 3g1#0yr>9s0 #'NڀS;ػ=6c a87I,</9{7{Q~ghmm\mǵj}y˒"߁}*(cFv[UQ\LQU$&y p52$f\BKZrH6NiQV !p\<>8<2=4Zօ O@xe79yL~dk]Jri>W+eN@n!פbN9hp6U=YƄm) Q#Fn ?? e-\#.R?")=S}|j P=)E-sBc3ezs:n]܁!qQ"PU}7>|dJzy׺`-XIXp㷕<_"<pphiX2u`$"9x/2̾24ɞƊhnb&{D>4Rr O8脕FDSy+C(bVUS stQJyE.>R(F 2ȟ;2ea&t4~FiY﮵Z1,8ʯ$G"dY(^6xǑHP1\+{qscCZrrVe9Nhշ4,3:-"w+8]Uq8<]}6oln1F3 ߏOYQ[g>d%%}χ 32q cQ'K#Ɋ0yEK~3;ya+3U2.9#ܻwzggǷok/5jX0gD9t($ c8:Gvz-i?%28rdƩO`˲kɈ3ތ!}[(~wYEn /TeaQEc8Q'&H,E f 4& `*2<;0҈=78wS:]f4 a8yAlf!;1וÞWD.K,#DbɍSyf9vErȨxKdnT4Pr< T@,,5QEXYbֳ6G܉)EHjNW.\>_o64ш)g8g&N,/yl'Jӑ '$-5N1Yҁ՘j 3 z.ǖteWOi#^g:0v-/f.!h]xغ8Gdr'yrl:5 Jс(=lio| "b g6x"X"˂KAwʡ˚`,Xij8D)lI5c ϶UM(YHfg!q  &m8H(@1jcQ-?W p:ϒb Rz/0o ^Ws簓4 2֚<:;jfN8~4̄x"wT{FVLR̦._RٛU.0&na;J\0d`e@98=Jen@839|!:  dXч;$sdg?SRcNsT%s{ U Vt yCyNс2x!k8&O;2"\ g a˽B|q J -0/ӡfd/Vl4/hH??m;> #~zY Ĵdh ꒋKo"V :ZfaY_n `QIINEB$ =8=f&R*gf5;4M ԥngW#w^7eXc8e5 1lȟÖ5hY *6 QYp=ejd (,%;t"yTأᏡ/8u ͌JOԱy7 's.c]&>5 å{Ifpcg,/2?OJݍj+Hf;wP"]@>l']82睜POJ E%$Y/`5"d&9fhQ)C4c0Xi};֧Xf f,38J/bBa)#%KM଺S6pUǑxm%,ڔF1_PdL|6*l V0h?IbǞ -R8D+.> zz0I€2a xp}P.|ǚf[ aF *gJKL*𤅊(&UJ̸RIW`h5 RYfKU!~ا ) <˃΂h1Ya, BwYS%v3\V Y3ɒLI^)yBX)/5[0q 7!q.\*`<~RpY>ز'O]ׅ@~L<|ϧ.0@Y!eTX! H0A-+ʌ0r J ܗDrѳI Nj$)?]|LJFA׹|h4*[<& dd)Y,:X6Kə"EdD"Q#SiΪ"?={:1pr[ c(ITT^ B4G JncԴUڝvZ(S`79FC^iAN#ծnU *Q8'b]d"aRx_$`Q5%)yhX2笵uV{ЊT$G8qX C";#%?VEѫF;MRiaJUF]vMd] !Dra9 [__S9VY׌aUhgO$ },%&%d/جP!')l({ aq*;e"[G)G?JTb$! V8I -<9e0Zu`iƼuXˤVbdBT)I4ۮcի> OR%{A&XEBI(0QD\C \HБE" Da , l\$YSVuōnTڒi CofѩVeEg$1D8V6iWSEI!"m^؆N +P1MȦ`$̧3ŀ;&+c"(@0x{4u:]x~kѨ-OLUg!tw'Ĥɪ=]Kh5jNhdq`<{}RY=ͦۚ80gR7?ZpݵnjVNK8{oO8Na4(= ȕߋ (,7`gvFwƋܙ{ɸ;;0y!#B!(J9$?e4煪Bc%$,m0ԎPo/Z$FpJfΙ`'q;1ZUWJ3&v4 }>̙>{ܽx'rp7pUUC;ޣTpMYM2ˉ9 _p ^ d $*4ya'8'BԱ*L9VgFytO)[)EG4{EcLw FJHQt/}bcXJ##c@A0耵NZt t8=P/҂ XBV6VT]I82o?@ESut߯RO//J{6`ˀ5XQQ*(9R@dDa:}޹{pp@!ȔEj,sɫ,r8ef:iV*h5vi_%'~9_h}k_oɷ|t['_,tO9CIT;Ei z9 ʖԇDcKVjVqOpl c'eb0/s{k 9lze纎m;*%jGyZ#c =xT.7;YF|{ށ$ ''rҽȉJϦI㳷4Bb%rXCܴG"1]JhcD B̑׍Vĕ&,V1̈́x`懲0 =pa1f)3d:k;^} v:/fl#Ya`>^6m qxL$W(~nCyݩC n`y$aߗi.ju?FZIx_xWөJ-WzeC!RB#梹[`T$Cj\[VԤ nk꓈=Eх'M5tuέ'oWwn0E F^!gN';bu=g$5p#ncўRm7R̟M>/rU8IU4(UkYZcZghdzZ/8l['G.Hz,Xl`8 Dn2 mM)QP kZpQ]hЩ% o35s!0AWaRUTVU`\3/&☐g3,l3in$r$(Zd^oT`;8Q]PkV2`E>9F9Mf Xhw*JYr>#biw<?Vo'o޺G~'>y}Frt,{Q3>fV䒢V筵ҹh_3;[Z[[V͒aΆtf;nVb\UK~fJmΈ6gaaE3`TxcaI <2e8#F+t\4i5ORzgko('KZZ%MNo?؝8W[3)1d: z'[.rM!,|pxAf xܦc{ggCpѬn)ɀX4~5eX<ʉ: RHێ;9 %LYlrY0W" 639=dM1[Tp-Z^[#yh0C<ɡw6Σ6Siެ7=],Iv2`Lj!J >,]"ppTRDcl$v "D%X1i Ȣ|1|ѐ52s #b@lEESX%jصS*Vi27UpS|,b Q5:댨m4rV FI7 A ԒysX &SINBz\78`ҜذaCT.W?[@A%[0dXE5eYʪ^Da6@)' ɓ'Ox>n[AF^5:Ed_8?O`m{Z;;[ׯHOOFg$̿3,i~)˺CQUn7}dǢJZ.`wB/\ƹTX-)Nl-EU(1%Py2LvjOW.@(]#` -ܹ㣉&pRsVo%"N i!Wf$T;vUx"-hLZH+UBaL9@oxv2E8d>yK@O jd|Vvֺ坍F_dmoo (J ؍aa,«QRBz$$bX%O=p᧗ Bb E:~$ ٴ-S{ 'U5 #$9giس/@ԍxQ;yݻ卭Ϭ| H/^ DR#ZN%IHs5@^s15TbcK2@b:/g :]2m5J 58lѣ;9B t6qf"[؟)ժZ{nu4z4tMIdTUq|ڀωF2B`Sp@2i ![x]"u4DXT0Oe!\ЏG n#UӲQd\l,̧dl:+4/ cdz*[Vx<~j]7m;Y+CF Ge4=ކCa[ vgpp} MVX:n9<<Φ jMx{_o%S 6{IX_ [ ݻLU'7^\F kicsڨj4$ ێ ,]*W+eyi8E]7|SX-U4sg \GKֽۏ= _x{@I^a/zsU鞞ݙI HEf`ؖmq%JҢE6%MB$`EIrw{"˥J24%:g@OxR~FX_|z#SA1P+BuLt:)<*z! #d< TEXnMD~iu腁,h>zvgXdJ55'kg&- TXdJQq1gZ2՘@wc<|0w*2ԵOko'T0 %YSb:Xi$G/~ꐤ~&N{p~{P_+酹 !&Rf*vߵp(N7NOfNy0uBRH&)L9 Mz -g}9X'O];j+ Ou||Dd}d&Ϊ*3";N/IGQR+bf6ae,6U j% :Dxܙ'O)0 5z%xiF:UNq?(;5֓GW.wmEtRbn>rXDCT]n;4~N,>~]]^sxM˴ Y_4E1uͲ x^w ImvU߈$-+ t<|筛 Y*z;%bQ!m v&,+M G"kD(TDʤ'$Qu3%a_X?XNLr)Tr|ovogB-U1,d:ANCbv&VEb9b;s?7c]Sc0:˚vy] kY |!b +!k oPw'k++ń_[g󻫫g'XMst{XEGh8zfa<)7Q|C\I+*6ء00Dξ>C_*ED VVQ {-L@tB<l$$B&άbý47dsgQ^y{79F&+1D_,L4$B6ʊ8f0%lۅvU,ۤ*s|:X JIT_>&ԜJ8) Ԃ {yѭݭx@LC3"r $S\*{D 3tP|Gh0X?^l5)Oƣ7n]Vy%ϩdCpDxe0^TL`t2w3gPUmnlV%KG8XX^K$hz6 4$53iuK/;#MSOG}!SX*0Կ+K}ﴖw\.{SGE_[ŊOt5`J޻;bXlYQDm3++G~w쪪kۻ{_oY^]Y" x!^c[{5& L&xee,`{cUQMYcT:e(;|_-gί# lz# G6WZ2* 0m5@pCL1b2= wgCk0aPmS@~4]=7Ww:+Of L]3qhh&p:O ✩jXݽ/\3KYoC>HfrXZX^f"vQrx-5WN:\Gx~Z*3+^LyJ?ӿojRp';ztl5Zkk鬦bNcyhY啳Jqk뱡(M' (lϩJZ&J q9n7˕2L Ԟ ߻˺b$Ӂ3ll4MQdA'A9E|8k&n߾}pgY:XGb8|ۯa-j5vo[gtg+gC*1:Fyo}Ib^#I ױ`š!ٳg3|^ zw{c;[|*Yd+~/d>y̵tXkZ*O:}4ܺW.$⹳H;pq ֫ÇGIEX^ZZ".Fޭ[=x"vZ (:<<k?~X 'h=6 gSx{:(S ?~};?׋ż+E;,bI3 |F^_wm"!=K^5bdzn.ΣO\]+()։(gc8Q,bf,>'i.OE]Y@ gR_`)s<>mJÈuit*9a)O>1M 3Z)Wa* qekO>NI<(NnD1I {}B:vmaqf*1_ZD, 6L`.W{voD>IbS5@z&M(Y ǬO1>L`\J݄E2dtBEV1qVY1Þ'zxj xt|E$L˛aVWICׇ͜Ƀ55>Ը[K|Ռ5z=7D1pk>OX4i6ML3!Œ9 pw<;+&^48C%$m|H&tM}x"Kbİ V44@]5_U8ݚ7Ν;CVV"xćXư&x=Y5Zhf_\Y~Yu^rQ8nm+X`o,;?_ZXRI){p5NJ%x&Bl:';*@!hLўls0 fBye&ޱßUWLң}~D< drS<,nkCk¾a|5=--"= H ^HS*w]Ɖdp"MͶY-,NF\!`/Wj;DJ RfgURD@LTl E' <ϷVHgN\jGGlGay",?9 JdP#*MtQKF?J.rd0(;#+Јrx|B;fZ_44)M`R 7Ƌ10m|FO 5bu.V8vuRs-"NfGUG@MƄ_8b#JiK%,NL\R1!u&@ϭ~/p<"Kqz0L/ ĖHrs[:~M%ͯ)od~^6a`מ y"q89ګtʯ/bo3rB#DؤrmW.ә4/ϣ txpX,f8`.Ѳ7_JaYB.v}xl} )&T dz^JLs4, cn7uf 8Nbޝmˬ^_ #`PT*~>XΦ7m)0̜-^1 |x2Bߌe !D 4Gi8'dB1q<2'1s{X7`}w3Hds<>!ƀD obZ~ NQ o .>eer]B <7,1 y-&eǭHs,˥ j{֍$™8纶vJ"I}gsʙ`oll\p ZUX:߳ L4|w%I|0GUZP%: UԊ\:0RME5ٺ=0s8#MY7ͨy1Qajto2u`[lp\*a/a`b;3f2@F@>x@=އ \M 0)QH1EOQ &bd4"or]R//c/RDLpF4Ω5007Lc6¼l6w\?3e1'Ca 7bc!lc1f3YlI #>2v9MGMl(7 m8+]pr9>3Ʒ) sT27?A)e<)t~uh&GcsFtKr 0~;ŗ%c' #UIp㵴7;DFl`eΝ; ŢHZ;t[G{;ۅB7j-]oQ649m2+UN*CyƂ7Rr5"s]]yb֌,%t#OTf߼{^6ϼxzΈR,bf+ 6 S4Kqswkc}q~CV&xo7- A0V;a O#ggq?zcP1x"T es#’,]E6O]T2wQ}oY\05%gaa`{ޏa+d2laE,UtW`Z]74+s퓿MYGiU-.,/|"II|q5ΓY3>k?^rP?s]d"02 f2,Dxl&n4IlOUAG>ܻ3Zaܰ~Lnw\#{iݴ8JkkɛΗFZ~ieX?i>,~@gl ˪%DSJ0؍hH9X]*Q~m`|\[:3#r2A&aqYy` t ,  ro{:^VR6xM}b\7/߾}T$trv,@goܹ{0/Dl \X7B@7w޽ٛQG ]|&X*{ME& M`<"70L:㾱DApTQDfS:H !㲒7iV"Lu@@Tn™R#bgLYPZV^ަLui_}$'?IvNd 1@0XHVfqm]]vzIl:3 0'XLѦ%EI2bao?v!e3G$Yځ-Y=,H[/*Ny1̨2f^\uRڶ[898k+ZիAwim)VfhDRhbR䵻w8vuZ b*dҌC\;$R]w0!@Pl@;" /ҁTx:ykSkj.Vc-"Sd+f2+G;O}rgqt66t `g!Bdtck4)Rc \W ť3gaeh~>{na ;7wo,u]J%QJ گb9`4P糴=׉oJG<2G/Fx2z +\%cP'x?S>w?W4EIg3erǟ~m#Qb<<|9AyD X*'BDőD8VVw pB)RlJ24`tXtv7gq0釓Ū`F"$r M b8K/Z*)\/vFlF2fЄ"D37 Vųb Cp@&3:"Uts_R?<2M'36OQ曵>Zd N$t̆l:KF9O5~0!=S?<>%-eϭ^[_#4x><4M-M)H&`#CgX6Kb:"M;숃@~/,[,8C7,\iNG@՚6חD^(L}td sr b^8o(GyP_ˈeSpe~* ҈aɫKGMTc2VGf%J6TFIbv& J1}ζ(%N3q'MI*&fN~RLm1/l8So GIY2sQ5JL TE1(Op" %+@J*0fin:Olg*v.&u}ο H~ccc8-/-NbqI*yt8rV o=g rߡ\\ES)]RίELX^Pfdg"{JzxV?lo6M sfi$ u{v2l*J^Tu+_Ɵ|){@ICfil2YIDR, (TˮhD .t}n/}_Ʒ~oC `MY)VEVrᔧ%oI9$  ޸tst5{d?nF`T\w2B& mO?\*_rA-2餩iUVY0D f=ޅǺ`R9V\3tl L3<(_O(+m7uU2ɦ[g:U8"~Hk4tR0s%P>c fFro.]K&%quq?UI*b"G>B?x"ljdq*# 2H-#MyF&6M Hã5 rb ݘҀg4I58Θ">j}8:vw?f4'y3[$0O^U3VHڏe=IAzm E|w*83pG$5Xaj\y̬ۻ> >|V<`5#/N(O '$_%lAĢ&$LaFq%]9LLj K30LR j؜:jKiU^T颩V'T΀1a_eS A9.pn}MJ?o D!C^hɝ`Φc#UE!U5ѕwS;GfI[?S?t)Lo"\abpu= ^K &UT,'ZxKu{𲏮;95ɀs!Z ASc_RA ?- 8 IQgxclʒfyK*yo6YL v|3X "%I.,.ĺ>zhc SF0>~bƒXi~ZQd1G4i>mit'㣃b15z.@ĔHzUJIgXEt`B6$JbSl OL]֊\竚Ne ~`420"11u*V4kVR7"!V"HD8GQ0M?מ=4$bo\}NFdJΦj34匵NIS-.$s_7@adY *zL,67+^q]mf&LϗIHqc11\/DDl͍a/];o wٹ=MՇDSE$ ۅHǾW)7P.0:?msآS@l5 p6-Y _O21PQBքJFD~GG]\.|ïحne]*l?<YP'bzC2Wz$pef_R2Ml8?Ji%UGp|OzDD̿*c'-*4OLQ u4E }Co,|uT_>Z^(IPG+:Qv9QgE?pݩ+&<*vN.&Ew;nOLIã<La̝_5{S*?iٮt|tÕy-v\XIgNԧ?.CO۽jh;JE,؀ tg0kx⵫tXT2rϬՃ8eł]X>|;#L tsuܸ{t4knF!V充TqO[0:/L*v?ۿ[) ر2{mʁ 4eD/f6[) K8Ō0.LCNr,Kr[\can`LX!誔e``h{`{G9_||t1I&cc==/xYVkHLz:;̤וTV(Y,)2>+Ej?T3yI*Bu~h TH({q@yy ۏˡZ.M,vR= ]@'"B/ 'VZ7)ĵz^W yJʼ]*Y -2&#o2;= ]rcsytwӡn敋՜r!yQ(N3|"f98IQtBU[xBP5wa6ZQ}Cѓ{ox{{ǍJ+p\2!o;3fGL!i i&IMvQ}1>ݻf\(xѤlOTpUvPBO*&0N(JNYvy^/B̕3g+leccTc71USZF1 < Sx ǕQuh]}1L̦jrƴA8+mI']/8:u*U[:ҧ~Nj`8iOa>.z2k$&KN%-[*x"l${b犻q7k֡H$95?{l K.󎟷х?h z{0k C<שײrl虩u ( ?5s}RyufAL<[ipI&v&f3YP@'*!mu7Zagzk©y.WByPUX.uT+Xb"wcN__/]#؏ܞ"Eedzl0Q|qUF*I\&`yL+$Twxl5 ݻݶ8|Cz׮NNɉ2C8+DO侘MsWn\-d䛜>N5NxR:)=b] \ sǘCMBbN=FU-7  ?G:V(wg?)=Br9W|p84C">~huB\PVtg?Lmqus0T$f3,8m;6 mDVlmm_ui͢|i ZpY]'/P54(8S<HR+++6*q,v*477 _%G6.3̀*7* Z-o<ƌ[pD4⢠^xE%Jh)TDKDj8R $ɛNgeLʃN__Ӄ;  `,Bl6 j= w4|?6cM;~G[V؇QL0)3#fńqZ/&9=O9s!uv 샋//-AokwxuB?:m:",ƳĪqcsXj,3kcĕI1 ?"Ww\d,rѝc[-9p@'85_L}y107N&䆌;>\T6 t$Vz(̸xCbqdZ&R)vэx}[<1c[V>O9x ZC+]j#pb%T>ф"UE"k>8c$Tn$~҅xOX>j |owk+e%氍 =HFK"b;n's1YcQSuNA3NuxеYZ2>Ś|p'^ )jR,$*[5_ yL@xXgQslf]TF)Eal%Rp>[[OA0 ҉,bAEb"2kǝ~ѧf>-kh0BPh&S~ul1H1tl{KA/10t,dj~r+SYiuye?}v7nqRWο&1LN58*5<eCY(JBlii57 }a+vuvkKƮ}Fh0@:  :;NcnFbtB ceW($Cs Rīxs_67 4OYI6K{Q:n_"_dVp3/*F&xV* 19a(eH;ecQxMGa8A; u?d't|-AIoV!ΝY@)IUU5CO'$TId2A>7ҭ_+g3 Ԋ ;L ZፑuDLqq>4Rȑ8=@ʰ!M>TKIgh޳}.\;j%Q99duQg]׽~bu|`G&?ˡ$ɔRf*_ox^5/s@I>Q \_$>$fK 0'ʩ L*=)%u$R6mݴG?Cl~7-0ޑKsİC.e0].J6C|x)J%fxgaέONזS捂9f2a,dd^_e 18, &Z&W;vvڇ13 ~Nʀ3LJ !cFf!D6Ww| Rbvy~ǁ <2:MËUssIiJ$7~*Jnn}~膛&;99ۉ?3hΐ]qny 岠~YinP'Nۄ!HCSLg0MWRa/_/Ukŕu۞t:\8$IBQkeV2B0LR<4Fo 2=seVsbRvv[xx.Pv d2Ey<'|-D ϳB ;a~A7^<<Ϝ[cˬoESK?yqoJʺs<ͩr@O)ÍljX)9)ly"`ԗ"YM}Ht9"a<_wSw0.2 %JDQ6q4vזUz٨y(aXY6Q.9?0JQ&:ǤBDLGI3yCjH`Kn#egdNcd{iRc& }S>5aq# ?qDM,;%TҥatL215`2=4$ɱcc, 2/ЋyhY wk3^+荇 Dz8n:,ExHzͤ$˔ ut"3Iq:Ul:΄],{|~r.Qv5_)/䋳 |vR&9XTQxTV?x6B0E^=juL⋯_l&#YDV0n<Qډa^'&6`ż +rgg#έaē>#*:T=S; &m^VL38T?y>JNyCq]η\&t˓tQDcw1ZXP< GlsٟD@po!84݁sYJA0uU ~7S7G_YvQ:lYcoIj+0EÚPGFdgFޡd 5-|{'!8rTkJ\*xB-M%}O0,qÛ`}PvTE "&*!٠jouv#Wlj(H[Ll+T\L*+c&uJVo6v/$u-;./eX({BQ*1L%..GARΞ%z}N|[YvQ lnlLu5.&ZOQls]t:E]0Ǐgqwjk6ha'ø)q.\hI+8l b'MCP"g<å6f v~}Ⅴ=|K!Q=wͣB6_)WH;i!ngysPM@`Aޠ\|ڧ>qN;}in]F Ԏ:R} c2vw릐Qy!`"&q\k/^bJh<<+W{Ų]:Hh2_!EN2z2 M̤0.JuSf6^w7?H3X*%K<8ALq@&rʓ'O]娈$,}~ƙݷ.^)d(iB99YywnꕫW4q]Κ#CFٯq  <- ?۫˫0F.Y qb^ۧj1@Zڥ^L pR>N'{{{}z-_jt,!ĘnНHEhqQdZhVVuW/hۇ|9vTq$kѺ(Uj3E ˋ$J޹xZ Ν;4e}jK$Bdi"՞ř=NeV'\޹s/O}_qO;S! AQ DZ($V!p|ar:0OsssB6&N B:gTG^!;@ӓ δDô*-dW*uv(g"4IAbPg1,yvkC ѰRIo^~+֭[X (yCB:qơNV_;jn~33΋[ 34_'S5i~ޝXZ[Y^Z۟Ҵ_$/uTeD3eCJ`d+eK4PR*U*JƤ9؃AzgJ17յł@tQ8 HѱN4[I/=taä́w M֊jOj̒BEL h6+F`3:R^A<-puX\vZ[;Q(k|\HW'~'K}R+,$֭LU??߰rIž '/] m6`5pF˶Ņ9a3+|.o~30VkвWVV7u޻_ߛ}0`'($D)GNT9\\Nd(Vё%Ѳdh. ĤY߾}{#`M{sss {W1ᜦEe͛?9^KUmqiO]pnux@ӭ۷2I&Ƒ 4ҥK1 ;7J~?U,tZR>{e8f\ ΄he'7/]FG{O`2Q_29gGbYJc &9 X* S.VzvHEE[&S@aSO f&?MNlHg^}9.;xw9;%d5vΦ.VƤ̚$sx4P\!R4Gucך!,>Oƍvk>)ٜ!Lg-?p/ܯ|/u~}3Yg׿r_?-{u(LdjJJ,MWH< vbmga)NիDn)Q H'~& -Cg29|p{ssH0j'DQjI5 B!_*)Mw|6HrϦ-kJ|AQy#=4].-_jwAi_8VT&o.*y"W˹xNۻm}뢪lnm--.J.d2x\/>!;Sb \oU͜%mܿs߹uj.,-B$9 b15ַ/gyi;c m!3+1?'ՀUo0}%Eс֍VD}w ku0>aUk|]]o-v ^MR<Yi"nLJL27^Z\D;)P9!qpnnƘ,sww׿<&ol"RAyn}; nea);?--`e U,'>t&"Lˢ`ˋ=h4$A`>Ք!-~J'A 8~{ $ņAbB$M~n!SSl,.*Y|˕:|*vYꍅr0$qXiMQ߿>z{{{{Q8^x![, `hOJMrA]YX]~K_~#eg߄5ҪV;x{ʰyoubֿYDӹrޛC61g gˍoJzn~hoݽz ps6~M!kb9o# 㓎[l }6q+* Xʋ^ 7'o :cD:-h|:Dh" ۻ{}?'_i4a:sɕy}XᴸOc&+Xҏ͝[nI~$dCѠ3Y$皕ҝU4~%2k2o[[]ɲ#V,#lݻwœ :' F~jq`OӜYװKbHvaR%ޜGwp~lmm>|F+o}}^MLcqO}W*%F6{Ӯ2NS(Zi4[G~)E)v;'ҋyr {uxr8ӭM,˵!,u=5 Ǿ'>bA!@8ww|*닋J*|g>̌g~~y$Idoin;i]z2L\>c&ؒdCCB Uk1R}9Ŗ&q0=0n|pbXXZ(WZ4À͵v3c]ڤ=\+A( Kd&CZmaa.E tD?ƵޮUz,͍흽g}&vJ='GG u5/kGWrͰ NL+rQ? f/L=/(z&L63$SSڃӹ[HUVG.Ё//v}?אN('Ȗk-BA G]?\NcJD^WMQBCtFjhx;Q{8]3񓼀9.nϛ@S6*IdR Ο[- ǪӰ@M.>FV2ЙLc@^E9jO¸0(US$`+p8.+ᰘ1x?%0m~ ΋FZثɺ{ 60E-K \HQUmgiى"旗oyQ"~5٨3aCCF@(U#'~ӥb]lğ嫋J7LmܼUi,'M5Uj yj懞?B*R-3>f Ecd*vATYGnk nokpVNv{׼O1IjT HECB!\'bN]|qYy`<-YfXē*Y<nIԈ}Z"n\X(T+B* з+xZ6MDSLa02ԍT tl3ucduO ^yENLi4ӷQ`t\K=FS`) >VtrXhw;-h3 &d꒡$`C\R8\+*)I +xDBLeYeZ s|4aVVCj^ATK,Lyiă*i4cWVSJ?v W 䇔LgJ&;m ~ڬ^R n[bo:n Ȟs!9_V1(RSd M$nuuUSXQEQAWֽ[7ۯ_~%V?i.Jf_?%F( b?qQ9E [''|1ե(kz}\ScY&S:e34'G{} arrTH$>~KI}S?}! Vbe`Tڗs<|&K"lI l,.iJVV0j R xDEĠ\fOa fK[SlwoX^~P=?rpYcg;/Qz;H&_0󃣭BdP~^|rvN7Z$9c*%Hb}SMQ|? 3dP;x69vnR1 CW`ffVf!t6ˇO-EASٝlT'&Qx J"i9›d6@̳ rCbإvҏxyk^Q1AMDmበY\bitx'$].zqHQAA-E0d4 N|5LwҨKaaq"fg%l0'Is K- fΌԤIQQCo5erQtkH//rYE kܗʬv9%|`|%*;~{p>x"DT6 Xsizv`ٔ8Q:x邟60]ĖrJ.SAH"psi& [<6gRx,M;by t{$]L=WU~`ssfWWvFccZ{WtDq miR +8,?J*P$Xy[4F;1r(#*#<, 3&eILނœ $%@c*.˙ZIFf#Kk(ih O4uQotP_n gbo)wCiUe-l$XDLrrXp\f"M3]*wNQ1ML1]NcݮDe1[N9: 4g?vo4zm=J_2hC59!]!I3`.$F4+ƅzOԷC$ 4CۄZg;UKH^Ѿϛ!p(ѐr##7yo |I?W%Û'c,Iװ_\fDh:=CկxOKas@K-`qЅ,U2Lr/4TtŢ^)G*@@3ycjM[مkO9jsn<`eq$p@Sm,/$Gi]': V6)q`#4sBM"8 ثB&9-,S:a[kB60u:S֤5Nl.W{.,"$;yt? &~ ղ34x&&qJI4* i/r+1i"qŋVV+aŲx3`QۤyQIޚ);X#Ǔ/]{F};D҈Sml 9h!"Rl\[H\(Ԝ|AA*V*aJD& Jq1*ELdXdXGܝEYU+٦<:rxtĂgy&W*(3z-|s΍àV-w*ba lþwjMmaof ! b.WDx!h3Sg]b$U/-檡ٷot$RȳXߙaI3,2a;b _nv=; | bt!CaYAHԢ+DnG=MK)p͉){z! Z*[@*;Р|JUjA⺨Xi <P$*,I?c A)j·c!4 WRlmo[zhD9d$eLrny~_Q vDh!Mⴴ4AwJߠE"%L@ՄL }D\qlrZ`cNØM0 ;`!VYJ^聗/ؚ28pN"y#I"^xq6#xQZD܀#r'3 |0??DC5ch؆ B2DbBrbI'>t(5b@pcKqȊP{p¥ `i(&KK7)VatH[#cA_dD9X Fphz ɌWJe9d|*=-7}=Te<;#n hxqMq.2XIn"J9NK:"d44OC7)MG馌حk7׮wDK!V`ل8U@dI5.Vǝ8dQ)ԵϥiɌX*%p͟ލp2C\FtZˉI#'q1#*usSn3z%yD,NuAO%޳O_hC bQaz^g.㛁ʵvs^;,|l%x ?j.Tɏ5, cUdDa=2+/!rLe2IEmg3ˆA[{k|c0sB3_OFe; aJ3aE@}jP^l5nZDXYyę1D'97p)9;n\yBL_XH|IVMrE OJ xa/ }:Z~ٶ)7"W2ѠM1 l*A&So3TNyΩκh8bQo G\8|W\#p8H!"$h?*4q ߜb+2qKJBCIB̚/#!`'\ϔ]%A(RH}ؒ8QKr6=L".)a O̙nS&:UG8>e~~~0%& ,FSS*벦"yD.iVFc9 6+2VE,y-2Y2 k<=kT"HxٻqYw@8'D$I;zDg|b$@tM܎ e-?aސWJgAkL^Ĭpы~TPEfr)2xvީdĹ,I]A{ 78NJ1-ԧ1$%d#iGްg_d oO~?:hqqQb5JTRH/m0F 7 hΛ r1VΈSoΆb+4Bqrй:9rƆ+~!R_MBU+#s0b eh=aJ,} 9AT̾|tŏYBJ? gC&Hy"Fw}%\AUöd8%Mp|/-gӱHVl6{ra0$p"{|ARؽ;Co_z㟺/_j:dm1 ftS B;A:#xďgaSg%| wb~?ħ }xj.muttBVxmNzc 4-yӶ'N 镊Dfp;gemXQBәm\3lx@&S*O}HA?gen_qgҰ#1`B4Y>":9&q9R4ِˊg\&?a3l#Bä0E\@il5b>*dbBeZ?, S'S%6@ycO|)FNBN,cC=$GBioš/Wym*JŒYĚ5RYY/4Jkaev sEAJ#J$@+)pnIqf4-]!r%Pu 8c1Z_Rqz$@:\ʞ<8E0S(Z:Ds]͆f~q:T4+i4Y-{E0 !BI^7bOLJ%aAV#QE|(lRrG!|Y{Α­}[uf AImattY(M)S&s HۗWPC!H͟dTC#,T5rN9hX_}&̙>SةUYx<ţ@nĦ̍3V\ZNst b<&lHH,ce`d:GɸSfQtk츦 1@)a3LM:!_Z N˘݉rՠ sĮH~>|reIq!9xphP({Cl?q*/M]b%X<09Sc<녵zVUk*=ʞ R?N|[{&yBgthZCJd12TbjIECx[- DIG.e}G3jMJn_"/m3(HU.^ψx}~ժU~׿EB>W*jJVRvv˿AopSTt=\H&Hi;R(Uc|e?F1pt "v"BL>)/gd(xx,pEb,-Lq~~.|H f$cA$QdW>+d)A$xb~P"8BTI;츝N 3h{{1S霭/KgY: hIJA O$G.Ӣ @5څO[x!_*e쬕D&qԙ-,FQ` < J \_ #)Pәrk˖x gTSǬL}4RBp: rV*'zO&x1d3!płGBtX{?Q5eWx KQ3+1͵AXϦ@X**͝??XَT@t:L^73䋍2)b*|o4̬Mmsv n4ei[6x4B ӴsȆ|(߈Dq8ԛNҙF/Hpoww}p7?=r>%}H+]h ;}$kT`I)R?g*"Kت Rl R1,F.7ľihi .sΎ:`OqIr|+yuu6'{mPX Mr##NɋaXaICQvᆞ3V.5( gHib]`4Ǘ0kW4>I (S:LQ1"a[9Ћ/jO|G >NH,UD1%V).[?{q'p u-0b6$B6:&i̒2PJS}7v&X,xY,8#$Yh1>_h,,RE9.|nn~V+hJTk׮Q{Ua^6N㉎|kȚ2xDm@fDx)3ziO.VY?=5A.P:,07Lᓾ rB%D܈*Ftw_~pF @U*`|]|hμ9{ްsO1NN4 A*.ITAB;/x)cfd\Ow")d2hNm)iNl;^d:Mwi7KRBq' c|'w b_!nYWGΙ\\Uůqghw-#lN|ׁ"֖ .@ zG 3<ygULLCV@')=d#,>(:;a _:ƍ[Xz`a./-L&SꛛpGj47-]3x K8x"B?1dCK!LCvpj+`e,鏘P;WY9II|P3F6'  +E޽j8%zQ,_/fTj4p/O._|#|.[-Dr鈂2arI@W5A1p vtwyGX E FQ c2N"(mFx<]y0ruiޢYmmZ+TDό "(}_yip45AGt"{v(SMd\>N2'WFj)Z6NLU g lM튏͚ʕZy2Sߟ!'6&}GA3S'54sCKu.Cl ѿU|~5R% Ta,& OON:{¸(:^a02MX$gj[LHP|!NJͧ4֨Cc0HEy4쳛@6sE*Y:h<跏KYQ3!Q(R*;N5YI$ !_  ꨱ̴YqNK!|芠r 5 _\LQez04Pgǩ.j5Er! OixY>?ܳV{\xh?t:+++|=WV7nF檸݇p!ˍΟ?_VF<2U* hG\pakk W0Ս-]Ùd ;???))xT*X]Ypjz'&Sk&z$/cg z7!2GDi*˫ έovT ND'~\`4޷syE5.ӷm|qjႱIx04W+B;$ 9kG<{2Dc#L :.K٢Dx!Ⴧ W$kN&1K!@(~T 㥏06wyG^/j"&3s႞]_8/x'xu.\<DŽ7en/e}T!e "h$&p؟&IB;-"j vs?WQ:`r"#`2k(Y) HyR60}w>%+c·tַ37 *JVc^2H|0":>Vg9PH}n$9O[387XlWlz4k@Tf3٭-DA_8}d?4p%C-'"AR)X4 xVuOG!t:ͣ.'TgC,2^ s5hkqX:-'SKZn+-.>'x %Gܥ[L}&l˵ nv.TK 3ϕu V\xl~%kOk#6 ;y9 /}_˶molnj-=!,ddׯ^`ӟzOBp\kpjYf(z q'؟;;;~C/m/_ \swy>_Ғt6u/|a A+#;g/T`!"JM%A/R+v "'UϘjZԨ"$$nvXP?Oe>A Rj xHTĕ^@Hp<6OFB,JOBcl4Ouh|aF5Nl&9w"l{/yrrNŋ/{}\}nFcxVP, q- %ªhpS7Jr]FG\ߖw I'c nK R U`Dy}p~G^9HQ~P_^22Y0Xʟ|Z?y7+ucǃ>"BC:vPD X\zv:'DD !T|ٶOg2H7"X˰Xfb:) MDt! ,Ϳ߽ט_ׂ0$!2kROu+&w{Vzz10@twTC}Q/tޛP3?qMY| ׿u.| _@t'?GE y`pt&a2ʼn|xx[[[>|OpX:{ןz2!8K!HJ{{'apӵR&ccwZm$llh<6&hSNN,C<"V:,LD׬Mׇ;3@;VX22^8#_.W~n8;]~ZoLUUT-a<:6rŲ!VSl.k"0aa|/{Y~Ja3=xTSA\$\w?6Aː8;`)7+Uv7.^=wiq >{9&)Ko>6u 譓ESaRB̉NUxw~ _|T\:Lw67Ĕ^G ʼn뻬NNYcaH /ĻwF!r?_`;}Kv&cT4CU/v<^$P~P-اAzHJ*NFz`GGΝ魛7\v>m/.$`@А"c/|>Q)5M*,,,nmn[<7 l'k}.^;iMH7oݬJn .6[6= xOYI"s`neHQt+YSu{e$xdȺe"SAׯ;gFTYiٺ9$M`|rv@cn[GqWWu}N\P.|֘{|{[$KK7|[8ͨ8U V>fш01|ֹ:"no,(+kw6c$rG)By:N.\ZlIDfiKM[JuzsKwr|$ѝ[77jtU &>XDm21 hZ3Qd4FLZbrGO,9$VJtEn6KKJN#~g+kD pME@"iza0`i$EՕ5>pҒ{h98@2ZΞz{nؕb۷2TeNQ07^+Jy$)lD'0,8:86d/anmU빰iw]D}\oJE ]n8O7R>WЈַ__>Glloybww=}g]zq}=ٝfNdW;x>zTD5W#S%5jfɔv IQxeiq7l6WHW|$ƻpgƛPH>bّ4sv޾u3/^:w C͜;| ]-L0\:u ΪpV`8cANpxȺbPYr.? P^tvޡ@1𼸣^S$XE\G3vPzcd^msS?Nf4ݙׁ˅ݻiEӏM[;;ןQ&a)q'Y3p6SbJ D2JAqbČ":a],$Hxΰc5_O1z}}GZ*~["Udi 3PH!EN&O?j O@$zrvd:@ZHVkusz¹uZp\[zY5l|sc1/QáYW.=jbDveh-yqQ.lMO"d;-pXf:Kftn;xQ(ȠCtR4<\T%)EZB#<08gr fZ\GS aww'DN{"Ogpj@t?7r߻s ׳="5v!n1 еQq._Bt v˚&{h:&F'J)0(4 {z !MkI dFeJ5bޠЙlȧln:mЄr MJy|l=u p`6Cd$&BLQZZ֯<҃sx,PT>Hw9E3< " Cx@,L>CӘ_PdR͆ůb-D ǦtJ5x"P#u*h9/P(TڵvX##r~0dݴ$COҚw*) Gׯ?]r-S,fSZi膕M鏜u}zfH؎e23% m>c#% $;3Q$Mګ}[$B Avw]9Ͻj20|?v>P_0?##]|&^_N ..Jfͨ7{="V*S-chF˼Dn"ǺNy93cE?XٺQ& !bo:U~T<'GbU+LFzcwwAWmY.e ap=@KeSJJyqdL]J06pmc؁D9R䮩WlsNӨR&4mAȒu=AjydZ&&Kѩ0 JaUmZ~?={vuս#ږGGG_뉮7y6 d:+Qg}%fU׾zz%"ϵى]o@xxGlK`+xZͯOp2썃ƍ; ص( `P\< dP^|p݅2Y QUCjI;Cb 3]i}z~2=]`KS0m,N=džϋUEx' {xY6 "f6vgMe7ßmox?xx~ X^w֢koֿXj,kϞonMq^LtZF'@z𴅍q) EK}}+gg+'On -3gSyW: cȔH$iZ10gL+XT`(֍D^* GPMΐ)\s|vǣ n+i'xn8jRDhJ⃇67W:ӬJUxhܽ~:d#W<Q& d(نu*=)%B6mfoTh0j0K (=S?=|\_Q&$a D2φHb "KL-PyJ"=YB0Ewn}2Hq_E9IcA 12zn:ra~Wz=8*`CA(PMs5#,R]+:2P0Een 4̆>?ª pSmWE@,]H)ϫ~ۯ.ԖCXVI)l). u"`CۺLnN0iO<)Kڪ,\VX);'y؈0(R$:D|aj0,kZm_>~lnzydzuՇ}ZFVÊM&tiCMgdl`X1eL`oE~e7~|ٴ۽(xwkw.JL\4JDf&r#cj.i5 Ư԰Id`:N&x|G)}ۀUlIQIk0q@eK4 LL'H ZZ̔xjl۠[UҘgJB{22;<:|饛m'd]cfO~Oe6x>Be*vѐ' p4)?h{\߻z~z]ǏFo񻖱@ȐimBBDpdOϛdNjdaA8;Qȴ5Sj d;/W-D@panmlj/JJ NU?&yZk-IYLT&QkdꦚZWM=]=y޽x8r&aA\ ;WYWe(y)Z]|%)if *P`,n1PQn|خzj9$gcS4Ic를ֆ3X[nn#cinf{$m9sfl6M1j0˒bY8ˋi䦒*,%rYMF/OPQ崪̂jۼ{vv=IO.ѩgo7Nֽ> ȼL Vm}XSu}HW\%(/]lTxhuoݻ;R1iU\a5="!ѳ]cֶedEP)ݒQn4儜b&e2՚,N.5>Y0a{90n ؋A8nfpǣ\”y*ga6ji–4[s*5(q\aƖX*7+Ḧ́u05Ο䯷67Bȼ"bV!kMna ӿ ~} \ن]g_~Z^7Z{7 `+e!4(?H!TlO/!$a,Ǜh.00dfNXAa7iΝ"肐7.FMt,-"3l*iH6KbI¨h0.C<Wg;[M===>GK/&giL*1g6bf8HƆhw}q }@VlهmZblp(Y֤ 1Iy$K+ž/5|z,tL5W1ExyJ#KbSS,|"Tq6+Oa6q׶o_ɳn3w*7Z[ߘO'a0 ɦ1=HPD X]ZNT'i~=8O?*pPB{.Lr `YaŷUlqZ$R8 i$;_s)б싊_0_'VâIgF+ffp񦛅у_j~9F7ELc~f#Mr33/Dq m^??}給b&X掚faxe=(/XRe wAJ!XZQ 6v'|#Uj||ZN"8ЊJ##ppt-l沛 l"m/îB+p쪁bQu?'LؤdɬZog6:+ZqU{/n}u⭃[;7%[9ZS߻%Hb#AجJ62Oomk7e,QKecMSӲ"" >۬)(NP!1m4ٙ'Z]RIVnON'  ZdžDˆ ie?k˭Ou{_&A٭`j:th eZa ViU ]2)4+J2jƅeac+f6S<*Ds3f]s[fE*i?w==dJ)t{g涊P)3) ᧑u穛UknYz xɾ}t8~ݕeAo 꺎Z9"Y9Fg^54_W|K$g6xk2e%zWNβnڤgrfZv6Nrt_s٧|yznkQ$%VU ,ͪV*'Ϟ.oR'ZEl8(:k؁,L(/Gg޹Hc hK7ot7o;ˇOVj?#ɊR֯rX?Pznx&Y{)KʋMM;Od#âPh󢓇bL+^@$dJH& hDŢbq.`.X q(!I}߿m9NNN%7m&L0+wDhQ3XfTqI.o05?DP;{$7zZxLT5vh+mAI,9rLȫPƪVb]c;: iu?xJ642I8S4+dC *kL/ճ$&X* wj-2фKY_Y@F1V.3mc둤7e\!2 Z>+B9:Cji?Z+ije&n]*"4NLKLRהvC-=5{ZIޓO8墥-:-Vd5[{M`zu/\ta5b/QaB4=U0S{ϐ_V- Gp8N^td7p?7F@KhfjvQ{(ni* 21\.oU dfPp8pY1C}؟/_!t>Xiٶi鐍l°N4gWGGf;]&Fb ;J Rcyq*:VUVn(> O%`D^I%Y>l>m`_l vA8VpRec EtI^0!NfΫ!K^ %vqK}XWU8$6i_ k9Tθ>N o6O扔n5 bʍ?. Ĺ9O72nSK}_2D%|M|B'e̋NmF/{aL;kXJMxIb8=hл't0~eD#I.Z&׋L݃ITI$플DLm<}uxxuuۑ,Ld}BДVKAO.5j/Xe*րk$BQT箬+aU'de ܭ $kQ:{} WߨZj.Xe" 7zDE/F}eFC\0mf[NMGhf" <;B,#(Ϧ5T^=d襦Msx[4Mat%b7s6k((lj4H%oZvX.A3]-n6i9{McMN`Jbr^r>Uu{ O8k=e0 X: gyeӎ<>Ğ2UK*kk{`D< XF *0㺼[m1>:(cN罴(=zo}wVYD яR! c1uئc㈺e~σWN ?e0&928 fuz{6oUMy7јۛzN|Ft_fF |r_w/_r"J0bp[KH⪗RѶ,Dh#D>'2ID#&>F ViFweO"!."G]!d|ED-IPzf2)5ԭ4l<|h:]S_KrÈ '"IݑIYDSDMɀzA\D\  ktoMRY >IJyz<sZ`L~7:a9<8R;v?r \rf(ԚX|މCt4o.*i0 4ĮVPJ6"u j0x)'h /}cg^b좀fZE<(^6C0ީ8<7yai5)}&_>+X \i@3W ,^}s+ˋD6:r$[&yM.\M) :yĀ4}-\~Iep щ U :E<#cRfI޻_^] f2yuApyDsS MG?{vtX݃k djlIQ|-n0샆{XIqL8dx<p'nto<"rlV./0[0@㓃Lx<ɇ J#䡏C|5ybʰBv/:NF|~뵽xt9X5enԞdB!KbbCqQ(fc ke}^ǚ? _x~6ʤAbȕA֐3IENDB`rurple-ng-0.5+16/share/images/pause.png000077500000000000000000000017231124731324500177150ustar00rootroot00000000000000PNG  IHDR D pHYs  PLTEwp@GpF"E6R5AA LBDžBǍ_ lE3E3E3E3p__|UdBX14ԇ4,>|ZT/E7 ?O8صZU(OO8ZصDO3Xs6uHsxsqAA LBDžBǍȵ lE3E3E3lE3PȵZBXzԇ4zȺP,ԶԄԅ0!0Զ)=!0=gԈ0Ԉ*Ըzc!c{X00,<1T1`0=!0=լYtԶԄԅ!0¿=ѓ=!0Ԉԇ4=嘺=HԋHԈZԈ*= ldgԈ0Ԉ*2ft tRNSJObIDATxՒ9 @s?XhN3=f (!)Hwp A]`;XAY`Waqׅi+W sofdIENDB`rurple-ng-0.5+16/share/images/reset_world.png000077500000000000000000000015271124731324500211330ustar00rootroot00000000000000PNG  IHDR szzsRGBgAMA a cHRMz&u0`:pQ<IDATXGTKSQ_؇( +Aʴt/C8CS7횖,!rkfbQfChtϵ{ׇ1XBO4kXi)i8R/Hk wNq8§Wc/*ʉݯQ7PXzA+?턢t"w*UIENDB`rurple-ng-0.5+16/share/images/run.png000077500000000000000000000023371124731324500174060ustar00rootroot00000000000000PNG  IHDR D pHYs  PLTEvx t x )w)  &&((..<>33++&&,,;;55::99==  !!%%11>>33::((&&--006611;;FIY^DDHHKKPPYYmsw}FFLLQQZZXXVVYYEECCXXRR\\aaffllrrqqyy``mmkkwwxxxxɁʈ؄܉֑၀耍덍唓ꓚ⚛훤٤⢢⪨騴鴸!0Զ)=,!0=8gԈ0Ԉ*Ը|c!c{X00t<1T1`0=!0d="ZԶԄԅ!0¿=ѓ=!0Ԉ(ԇ4=嘺d=ԋԈZPԈ*=hPDld8gԈ0Ԉ*vv97'xtRNS|WIDATxc('{ $Ř*pqOģ9<[Ǥ'f dJ REѬA605HkD椇yI#Y $0/3#TY̌@a6wl 3c͌80FFXraB/0(@#=~ o#hqڣEZIENDB`rurple-ng-0.5+16/share/images/step.png000077500000000000000000000022411124731324500175470ustar00rootroot00000000000000PNG  IHDR DsRGBPLTE t  ''**''6699::++##!!((55<<22==3388AA@@LLQQCCVVaaccjj~~@@IIPPQQYYaaiiooppuuxxwՁًڐޛѵᥪ⪰䰼BǍȵ lE3E3E3E3ȵZ0BX\ԇ4ȺtԶԄԅ0!0Զ)=,!0=8gԈ0Ԉ*Ըc!c{X0:t<1T1`0=!0d="ZԶԄԅ!0¿=ѓ=!0Ԉ(ԇ4=嘺d=ԋԈZPԈ*=hPDld8gԈ0Ԉ* t t HtRNS@fbKGDH pHYs  tIME 8iB IDAT8ՒKN0c# .X($L@ W-8$Rbߞ#L dxA3lh( O'4M6S,+b,BFxAvn(E)61U!b |Im31_l C:Θ#aVkFѪdRLN<*Ϳz%Әw1 l{d-kɔW}#vIENDB`rurple-ng-0.5+16/share/programs/000077500000000000000000000000001124731324500164515ustar00rootroot00000000000000rurple-ng-0.5+16/share/programs/follow_wall.rur000066400000000000000000000003161124731324500215240ustar00rootroot00000000000000def turn_right(): turn_left() turn_left() turn_left() while True: if right_is_clear(): turn_right() else: while not front_is_clear(): turn_left() move() rurple-ng-0.5+16/share/programs/test.rur000066400000000000000000000002621124731324500201620ustar00rootroot00000000000000while True: if right_is_clear(): turn_left() turn_left() turn_left() else: while not front_is_clear(): turn_left() move() rurple-ng-0.5+16/share/worlds/000077500000000000000000000000001124731324500161315ustar00rootroot00000000000000rurple-ng-0.5+16/share/worlds/8queens.wld000066400000000000000000000001731124731324500202320ustar00rootroot00000000000000{"width": 8, "walls": [], "stones": [], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 8} rurple-ng-0.5+16/share/worlds/add1.wld000066400000000000000000000002251124731324500174510ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[9, 0], 3], [[9, 1], 2]], "robots": [{"y": 0, "x": 8, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/add2.wld000066400000000000000000000002571124731324500174570ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[8, 1], 1], [[8, 0], 2], [[9, 0], 2], [[9, 1], 3]], "robots": [{"y": 0, "x": 7, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/add34.wld000066400000000000000000000003111124731324500175330ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[7, 1], 9], [[6, 0], 1], [[7, 0], 3], [[8, 1], 8], [[9, 0], 4], [[9, 1], 7]], "robots": [{"y": 0, "x": 5, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/adding_world.wld000066400000000000000000000001741124731324500213000ustar00rootroot00000000000000{"width": 8, "walls": [], "stones": [], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 8}], "height": 31} rurple-ng-0.5+16/share/worlds/amazing1.wld000066400000000000000000000001731124731324500203510ustar00rootroot00000000000000{"width": 5, "walls": [], "stones": [], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 1}], "height": 5} rurple-ng-0.5+16/share/worlds/amazing2.wld000066400000000000000000000003411124731324500203470ustar00rootroot00000000000000{"width": 7, "walls": [[3, 6, "v"], [3, 5, "v"], [3, 4, "v"], [6, 3, "h"], [5, 3, "h"], [4, 3, "h"], [3, 3, "h"], [3, 3, "v"]], "stones": [], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 1}], "height": 7} rurple-ng-0.5+16/share/worlds/amazing3.wld000066400000000000000000000003561124731324500203560ustar00rootroot00000000000000{"width": 7, "walls": [[1, 0, "v"], [1, 1, "h"], [2, 1, "h"], [3, 1, "v"], [3, 2, "v"], [3, 3, "v"], [3, 4, "v"], [3, 5, "v"], [3, 6, "v"]], "stones": [], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 1}], "height": 7} rurple-ng-0.5+16/share/worlds/amazing3a.wld000066400000000000000000000007431124731324500205170ustar00rootroot00000000000000{"width": 7, "walls": [[1, 0, "v"], [1, 1, "h"], [2, 1, "h"], [3, 1, "v"], [3, 2, "v"], [3, 3, "v"], [3, 4, "v"], [3, 5, "v"], [3, 6, "v"]], "stones": [[[0, 1], 1], [[1, 6], 1], [[2, 1], 1], [[0, 2], 1], [[2, 6], 1], [[2, 2], 1], [[1, 1], 1], [[0, 3], 1], [[0, 0], 1], [[0, 4], 1], [[1, 5], 1], [[0, 5], 1], [[2, 5], 1], [[0, 6], 1], [[1, 2], 1], [[1, 4], 1], [[2, 3], 1], [[1, 3], 1], [[2, 4], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 1}], "height": 7} rurple-ng-0.5+16/share/worlds/amazing5.wld000066400000000000000000000003731124731324500203570ustar00rootroot00000000000000{"width": 7, "walls": [[1, 1, "h"], [3, 2, "v"], [3, 3, "v"], [3, 4, "v"], [3, 5, "v"], [3, 6, "v"], [2, 0, "v"], [1, 1, "v"], [1, 2, "h"], [2, 2, "h"]], "stones": [], "robots": [{"y": 0, "x": 1, "name": "robot", "dir": 0, "stones": 1}], "height": 7} rurple-ng-0.5+16/share/worlds/beepers1.wld000066400000000000000000000002101124731324500203400ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[2, 0], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/blank.wld000066400000000000000000000004211124731324500177250ustar00rootroot00000000000000{ "height": 6, "robots": [ { "dir": 0, "name": "robot", "stones": 0, "x": 0, "y": 0 } ], "stones": [], "walls": [], "width": 6, "world": "maze" }rurple-ng-0.5+16/share/worlds/corner3_4.wld000066400000000000000000000001751124731324500204420ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [], "robots": [{"y": 3, "x": 2, "name": "robot", "dir": 3, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/empty.wld000066400000000000000000000001731124731324500200000ustar00rootroot00000000000000{"width": 8, "walls": [], "stones": [], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 8} rurple-ng-0.5+16/share/worlds/fairy_tale.wld000066400000000000000000000004111124731324500207540ustar00rootroot00000000000000{"width": 14, "walls": [[0, 5, "h"], [1, 5, "h"], [2, 4, "v"], [2, 4, "h"], [3, 3, "v"], [4, 4, "h"], [5, 4, "h"], [6, 3, "v"], [6, 2, "v"], [6, 1, "v"], [6, 0, "v"]], "stones": [], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 8} rurple-ng-0.5+16/share/worlds/frank18.wld000066400000000000000000000002751124731324500201170ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[6, 3], 1], [[2, 6], 2], [[5, 5], 2], [[2, 3], 2], [[6, 0], 19]], "robots": [{"y": 5, "x": 4, "name": "robot", "dir": 1, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/hanoi3.wld000066400000000000000000000002421124731324500200200ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[1, 2], 1], [[1, 0], 3], [[1, 1], 2]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/hanoi4.wld000066400000000000000000000002571124731324500200270ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[1, 2], 2], [[1, 3], 1], [[1, 0], 4], [[1, 1], 3]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/harvest1.wld000066400000000000000000000011151124731324500203740ustar00rootroot00000000000000{"width": 7, "walls": [], "stones": [[[6, 2], 1], [[2, 1], 1], [[1, 5], 1], [[5, 5], 1], [[3, 2], 1], [[6, 0], 1], [[4, 5], 1], [[4, 4], 1], [[1, 0], 1], [[3, 5], 1], [[6, 4], 1], [[1, 2], 1], [[3, 1], 1], [[4, 3], 1], [[4, 0], 1], [[1, 4], 1], [[6, 1], 1], [[4, 2], 1], [[5, 3], 1], [[5, 2], 1], [[3, 0], 1], [[2, 2], 1], [[2, 3], 1], [[6, 5], 1], [[2, 0], 1], [[3, 3], 1], [[6, 3], 1], [[3, 4], 1], [[5, 1], 1], [[5, 4], 1], [[2, 5], 1], [[1, 1], 1], [[5, 0], 1], [[2, 4], 1], [[4, 1], 1], [[1, 3], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 7} rurple-ng-0.5+16/share/worlds/harvest2.wld000066400000000000000000000011241124731324500203750ustar00rootroot00000000000000{"width": 12, "walls": [], "stones": [[[2, 8], 1], [[6, 2], 1], [[1, 5], 1], [[3, 3], 1], [[3, 9], 1], [[5, 5], 1], [[7, 3], 1], [[1, 7], 1], [[9, 5], 1], [[6, 6], 1], [[2, 4], 1], [[10, 6], 1], [[4, 8], 1], [[5, 1], 1], [[2, 6], 1], [[6, 10], 1], [[5, 3], 1], [[5, 7], 1], [[4, 4], 1], [[9, 7], 1], [[0, 6], 1], [[3, 5], 1], [[5, 9], 1], [[5, 11], 1], [[7, 9], 1], [[7, 7], 1], [[3, 7], 1], [[6, 4], 1], [[8, 8], 1], [[8, 4], 1], [[6, 8], 1], [[4, 6], 1], [[7, 5], 1], [[4, 10], 1], [[4, 2], 1], [[8, 6], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 36}], "height": 12} rurple-ng-0.5+16/share/worlds/harvest3.wld000066400000000000000000000007771124731324500204130ustar00rootroot00000000000000{"width": 7, "walls": [], "stones": [[[6, 2], 1], [[5, 5], 1], [[4, 5], 1], [[4, 3], 1], [[1, 0], 1], [[5, 1], 1], [[4, 0], 1], [[1, 4], 1], [[6, 1], 1], [[4, 4], 1], [[6, 5], 1], [[3, 3], 1], [[2, 5], 1], [[1, 1], 1], [[4, 2], 1], [[3, 0], 1], [[5, 3], 1], [[2, 1], 1], [[6, 0], 1], [[3, 4], 1], [[1, 2], 1], [[3, 1], 1], [[5, 4], 1], [[2, 4], 1], [[3, 5], 1], [[5, 0], 1], [[6, 3], 1], [[3, 2], 1], [[2, 3], 1], [[1, 3], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 7} rurple-ng-0.5+16/share/worlds/harvest4.wld000066400000000000000000000010001124731324500203700ustar00rootroot00000000000000{"width": 7, "walls": [], "stones": [[[6, 2], 1], [[5, 5], 1], [[4, 5], 1], [[4, 3], 1], [[1, 0], 1], [[5, 1], 1], [[4, 0], 2], [[1, 4], 1], [[6, 1], 1], [[4, 4], 1], [[6, 5], 1], [[3, 3], 1], [[2, 5], 1], [[1, 1], 2], [[4, 2], 2], [[3, 0], 1], [[5, 3], 1], [[2, 1], 1], [[6, 0], 1], [[3, 4], 1], [[1, 2], 1], [[3, 1], 1], [[5, 4], 2], [[2, 4], 1], [[3, 5], 1], [[5, 0], 1], [[6, 3], 1], [[3, 2], 1], [[2, 3], 2], [[1, 3], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 99}], "height": 7} rurple-ng-0.5+16/share/worlds/hurdles1.wld000066400000000000000000000002721124731324500203710ustar00rootroot00000000000000{"width": 10, "walls": [[2, 0, "v"], [4, 0, "v"], [6, 0, "v"], [8, 0, "v"]], "stones": [[[9, 0], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/hurdles2.wld000066400000000000000000000002721124731324500203720ustar00rootroot00000000000000{"width": 10, "walls": [[2, 0, "v"], [4, 0, "v"], [6, 0, "v"], [8, 0, "v"]], "stones": [[[6, 0], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/hurdles3.wld000066400000000000000000000003411124731324500203700ustar00rootroot00000000000000{"width": 10, "walls": [[2, 0, "v"], [4, 0, "v"], [8, 0, "v"], [1, 0, "v"], [5, 0, "v"], [9, 0, "v"], [6, 0, "v"]], "stones": [[[9, 0], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/hurdles4.wld000066400000000000000000000004101124731324500203660ustar00rootroot00000000000000{"width": 10, "walls": [[2, 0, "v"], [4, 0, "v"], [8, 0, "v"], [1, 0, "v"], [5, 0, "v"], [9, 0, "v"], [6, 0, "v"], [2, 1, "v"], [5, 1, "v"], [5, 2, "v"]], "stones": [[[9, 0], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/maze1.wld000066400000000000000000000034311124731324500176570ustar00rootroot00000000000000{ "height": 5, "robots": [ { "dir": 0, "name": "robot", "stones": 0, "x": 0, "y": 0 } ], "stones": [ [ [ 5, 3 ], 1 ] ], "walls": [ [ 0, 3, "h" ], [ 3, 4, "h" ], [ 4, 1, "v" ], [ 2, 1, "v" ], [ 2, 4, "h" ], [ 4, 2, "h" ], [ 2, 1, "h" ], [ 1, 3, "v" ], [ 2, 2, "v" ], [ 1, 3, "h" ], [ 3, 1, "v" ], [ 5, 4, "v" ], [ 5, 1, "v" ], [ 1, 0, "v" ], [ 5, 0, "v" ], [ 4, 3, "h" ], [ 5, 2, "v" ], [ 1, 1, "v" ], [ 4, 3, "v" ] ], "width": 6, "world": "maze" }rurple-ng-0.5+16/share/worlds/newspaper.wld000066400000000000000000000004271124731324500206500ustar00rootroot00000000000000{"width": 10, "walls": [[2, 0, "v"], [2, 1, "h"], [3, 1, "h"], [4, 1, "v"], [4, 2, "h"], [5, 2, "h"], [6, 2, "v"], [6, 3, "h"], [7, 3, "h"], [8, 3, "v"], [8, 4, "h"], [9, 4, "h"]], "stones": [], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 1}], "height": 10} rurple-ng-0.5+16/share/worlds/rain1.wld000066400000000000000000000005141124731324500176530ustar00rootroot00000000000000{"width": 10, "walls": [[2, 3, "h"], [2, 3, "v"], [2, 4, "v"], [2, 6, "v"], [2, 7, "v"], [2, 8, "h"], [4, 8, "h"], [6, 8, "h"], [7, 8, "h"], [8, 7, "v"], [8, 5, "v"], [8, 4, "v"], [8, 3, "v"], [7, 3, "h"], [5, 3, "h"], [3, 3, "h"]], "stones": [], "robots": [{"y": 5, "x": 1, "name": "robot", "dir": 0, "stones": 10}], "height": 10} rurple-ng-0.5+16/share/worlds/rain2.wld000066400000000000000000000006401124731324500176540ustar00rootroot00000000000000{"width": 19, "walls": [[2, 3, "h"], [3, 3, "h"], [5, 3, "h"], [6, 3, "h"], [7, 3, "h"], [8, 2, "v"], [8, 2, "h"], [10, 2, "h"], [11, 2, "v"], [11, 4, "v"], [11, 5, "v"], [11, 7, "v"], [10, 8, "h"], [9, 8, "h"], [7, 8, "h"], [6, 8, "h"], [4, 8, "h"], [2, 8, "h"], [2, 7, "v"], [2, 6, "v"], [2, 4, "v"], [2, 3, "v"]], "stones": [], "robots": [{"y": 5, "x": 1, "name": "robot", "dir": 0, "stones": 10}], "height": 22} rurple-ng-0.5+16/share/worlds/sort1.wld000066400000000000000000000004271124731324500177140ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[0, 2], 1], [[0, 6], 1], [[1, 0], 1], [[0, 5], 1], [[0, 3], 1], [[1, 4], 1], [[0, 1], 1], [[0, 0], 1], [[0, 4], 1], [[1, 1], 1], [[1, 2], 1], [[1, 3], 1]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/test.wld000066400000000000000000000021371124731324500176230ustar00rootroot00000000000000{ "stones": [ [ [ 4, 0 ], 1 ] ], "height": 10, "robots": [ { "stones": 2, "dir": 0, "name": "robot", "x": 2, "y": 3 } ], "walls": [ [ 5, 4, "v" ], [ 3, 6, "h" ], [ 1, 3, "h" ], [ 3, 2, "v" ], [ 0, 3, "h" ], [ 3, 7, "h" ], [ 4, 4, "h" ], [ 2, 0, "v" ], [ 2, 1, "h" ], [ 3, 6, "v" ], [ 3, 1, "v" ] ], "width": 10 } rurple-ng-0.5+16/share/worlds/trash1.wld000066400000000000000000000005111124731324500200400ustar00rootroot00000000000000{"width": 10, "walls": [[1, 1, "h"], [2, 1, "h"], [3, 1, "h"], [4, 1, "h"], [5, 1, "h"], [6, 1, "h"], [7, 1, "h"], [8, 1, "h"], [9, 1, "h"], [0, 2, "h"], [1, 1, "v"]], "stones": [[[4, 0], 1], [[5, 0], 1], [[2, 0], 3], [[9, 0], 2], [[6, 0], 2]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/trash2.wld000066400000000000000000000004751124731324500200520ustar00rootroot00000000000000{"width": 10, "walls": [[1, 1, "h"], [2, 1, "h"], [3, 1, "h"], [4, 1, "h"], [5, 1, "h"], [6, 1, "h"], [7, 1, "h"], [8, 1, "h"], [9, 1, "h"], [0, 2, "h"], [1, 1, "v"]], "stones": [[[4, 0], 13], [[8, 0], 1], [[1, 0], 2], [[6, 0], 2]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/share/worlds/yardwork.wld000066400000000000000000000004141124731324500205020ustar00rootroot00000000000000{"width": 10, "walls": [], "stones": [[[6, 2], 4], [[3, 7], 1], [[4, 5], 7], [[1, 4], 3], [[4, 7], 2], [[6, 8], 2], [[0, 1], 18], [[8, 1], 11], [[6, 0], 4], [[7, 7], 1], [[0, 9], 3]], "robots": [{"y": 0, "x": 0, "name": "robot", "dir": 0, "stones": 0}], "height": 10} rurple-ng-0.5+16/start_rurple.py000077500000000000000000000001651124731324500166220ustar00rootroot00000000000000#!/usr/bin/env python import sys import rurple.ui if __name__ == "__main__": rurple.ui.main() sys.exit(0) rurple-ng-0.5+16/videoscript.txt000066400000000000000000000076701124731324500166230ustar00rootroot00000000000000My name is Paul Crowley, I'm going to show you Rurple NG version 0.5. Rurple is for teaching programming to people who've never programmed before. Let's download it and install it. It ships as a Windows Installer MSI file, and has a standard install dialog. Now that's installed, there's a new entry for it under the Start menu. We can view the manual, or launch the program. Let's launch the program. This rightmost window shows a robot in a simple grid-based maze. If we press the "up" arrow key, the robot moves forward, and if we press the "left" arrow key she turns left. If we tell her to go forward when there's a wall in front of her, we see an error. We can add walls to the maze by clicking, and remove them the same way. But I don't want these changes, so I'll press "reset" to get rid of them. Now that we can drive our robot by hand, let's try writing a simple program to control her. Let's start with the simplest program we can: we'll just make her move forward one square. That's the whole program. We press "run" to run that, and we see that she moves forward one square. If we run it again, she does it again. When we press "reset", it takes her and the world back to the way they were before we last ran the program. Let's give her some more complex instructions; we'll have her move, turn, and move again. You should be able to guess where she'll end up when we run this. Now let's reset and see that again, turning the speed right down. Move, turn left, and move again. [reset] We can also go through the program step by step, and see the effect of each instruction as it runs. Move, turn left, and move. Notice that we can't change the program or the maze while the program is running; we have to stop it first. As well as moving and turning, she can also pick up and put down stones that she finds in the maze. Let's put down a stone for her to pick up by clicking inside a square and selecting the number of stones we want in that square ...and let's change the program so that she picks them up as she passes. The stone is no longer in the maze; it is being carried by the robot, as we see here. If she tries to pick up a stone that isn't there, we see an error. Once an error occurs, the program stops running. She can also put down the stones that she is carrying. The language that these programs are written in is called Python. Python is a very powerful and versatile programming language used for thousands of real-world programming applications at Google and all over the world. Let's start to use some of Python's features. Now she'll only try to pick up a stone if she finds one at her feet. You can open and save the programs and worlds you create from the menu. Rurple supplies a number of example programs and worlds. Here's a simple maze and here's a simple maze solver. We can change the speed while the program is running. We can create new worlds of our own and choose what size they are as well as changing the number of stones the robot starts off carrying. We can access the manual from the help menu. The manual lists all the commands that our robot knows about. Unfortunately Rurple NG doesn't yet have a lesson plan for beginners; if you're trying to teach yourself, you might be better off using the orignal version of RUR-PLE, which does have a lesson plan. But if you're teaching someone else, you can probably adapt the RUR-PLE lesson plans, so long as you bear in mind the incompatibilities listed in the manual. That completes our tour of Rurple NG 0.5. As you can see from the manual, there is still some work to do before this program is ready to replace its predecessor for all purposes, but I think it's already good enough to be used where a teacher is on hand to suggest exercises and help out. I hope that it will continue to grow and improve perhaps with the help of other developers, and I hope that you find the program and this video useful. Thank you for watching, and please do let me know what you think!